From 1199c777fb2820bbc31947fb06fa3219022183b2 Mon Sep 17 00:00:00 2001 From: Huiwen Date: Tue, 21 Nov 2023 07:36:53 +0000 Subject: [PATCH 1/3] Migrate WorkspaceService.CreateAndStartWorkspace --- components/dashboard/src/data/setup.tsx | 2 +- .../workspaces/create-workspace-mutation.ts | 24 +- .../toggle-workspace-pinned-mutation.ts | 1 + .../toggle-workspace-shared-mutation.ts | 1 + .../update-workspace-description-mutation.ts | 1 + .../src/service/json-rpc-workspace-client.ts | 50 + .../dashboard/src/start/StartWorkspace.tsx | 37 +- .../src/workspaces/CreateWorkspacePage.tsx | 60 +- .../gitpod-protocol/src/messaging/error.ts | 3 + .../public-api/gitpod/v1/workspace.proto | 85 +- components/public-api/go/v1/context.pb.go | 305 +++++ .../public-api/go/v1/context_grpc.pb.go | 111 ++ .../go/v1/v1connect/context.connect.go | 92 ++ .../go/v1/v1connect/context.proxy.connect.go | 30 + .../go/v1/v1connect/workspace.connect.go | 60 +- .../v1/v1connect/workspace.proxy.connect.go | 20 + components/public-api/go/v1/workspace.pb.go | 1048 +++++++++++++---- .../public-api/go/v1/workspace_grpc.pb.go | 82 +- .../src/gitpod/v1/context_connect.ts | 33 + .../typescript/src/gitpod/v1/context_pb.ts | 136 +++ .../src/gitpod/v1/workspace_connect.ts | 28 +- .../typescript/src/gitpod/v1/workspace_pb.ts | 309 +++++ components/server/src/api/server.ts | 1 + components/server/src/api/teams.spec.db.ts | 4 + .../server/src/api/workspace-service-api.ts | 94 +- components/server/src/container-module.ts | 3 + components/server/src/express-util.ts | 9 + components/server/src/prebuilds/github-app.ts | 3 +- components/server/src/server.ts | 3 +- components/server/src/util/request-context.ts | 19 +- .../websocket/websocket-connection-manager.ts | 14 +- .../server/src/workspace/context-service.ts | 142 +++ .../src/workspace/gitpod-server-impl.ts | 235 +--- .../server/src/workspace/workspace-service.ts | 5 +- 34 files changed, 2488 insertions(+), 562 deletions(-) create mode 100644 components/public-api/go/v1/context.pb.go create mode 100644 components/public-api/go/v1/context_grpc.pb.go create mode 100644 components/public-api/go/v1/v1connect/context.connect.go create mode 100644 components/public-api/go/v1/v1connect/context.proxy.connect.go create mode 100644 components/public-api/typescript/src/gitpod/v1/context_connect.ts create mode 100644 components/public-api/typescript/src/gitpod/v1/context_pb.ts create mode 100644 components/server/src/workspace/context-service.ts diff --git a/components/dashboard/src/data/setup.tsx b/components/dashboard/src/data/setup.tsx index deea961ec487e0..4907f1bb82e4b2 100644 --- a/components/dashboard/src/data/setup.tsx +++ b/components/dashboard/src/data/setup.tsx @@ -29,7 +29,7 @@ import * as SCMClasses from "@gitpod/public-api/lib/gitpod/v1/scm_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 = "7"; +const CACHE_VERSION = "8"; export function noPersistence(queryKey: QueryKey): QueryKey { return [...queryKey, "no-persistence"]; diff --git a/components/dashboard/src/data/workspaces/create-workspace-mutation.ts b/components/dashboard/src/data/workspaces/create-workspace-mutation.ts index b45152033682d7..e60332dc7845e4 100644 --- a/components/dashboard/src/data/workspaces/create-workspace-mutation.ts +++ b/components/dashboard/src/data/workspaces/create-workspace-mutation.ts @@ -4,26 +4,34 @@ * See License.AGPL.txt in the project root for license information. */ -import { GitpodServer, WorkspaceCreationResult } from "@gitpod/gitpod-protocol"; import { useMutation } from "@tanstack/react-query"; -import { getGitpodService } from "../../service/service"; import { useState } from "react"; -import { StartWorkspaceError } from "../../start/StartPage"; +import { workspaceClient } from "../../service/public-api"; +import { + CreateAndStartWorkspaceRequest, + CreateAndStartWorkspaceResponse, +} from "@gitpod/public-api/lib/gitpod/v1/workspace_pb"; +import { PartialMessage } from "@bufbuild/protobuf"; +import { ConnectError } from "@connectrpc/connect"; export const useCreateWorkspaceMutation = () => { const [isStarting, setIsStarting] = useState(false); - const mutation = useMutation({ + const mutation = useMutation< + CreateAndStartWorkspaceResponse, + ConnectError, + PartialMessage + >({ mutationFn: async (options) => { - return await getGitpodService().server.createWorkspace(options); + return await workspaceClient.createAndStartWorkspace(options); }, - onMutate: async (options: GitpodServer.CreateWorkspaceOptions) => { + onMutate: async (options: PartialMessage) => { setIsStarting(true); }, onError: (error) => { setIsStarting(false); }, onSuccess: (result) => { - if (result && result.createdWorkspaceId) { + if (result.workspace?.id) { // successfully started a workspace, wait a bit before we allow to start another one setTimeout(() => { setIsStarting(false); @@ -34,7 +42,7 @@ export const useCreateWorkspaceMutation = () => { }, }); return { - createWorkspace: (options: GitpodServer.CreateWorkspaceOptions) => { + createWorkspace: (options: PartialMessage) => { return mutation.mutateAsync(options); }, // Can we use mutation.isLoading here instead? diff --git a/components/dashboard/src/data/workspaces/toggle-workspace-pinned-mutation.ts b/components/dashboard/src/data/workspaces/toggle-workspace-pinned-mutation.ts index 29449bfc692904..3308310dc2f6c6 100644 --- a/components/dashboard/src/data/workspaces/toggle-workspace-pinned-mutation.ts +++ b/components/dashboard/src/data/workspaces/toggle-workspace-pinned-mutation.ts @@ -23,6 +23,7 @@ export const useToggleWorkspacedPinnedMutation = () => { return await getGitpodService().server.updateWorkspaceUserPin(workspaceId, "toggle"); }, onSuccess: (_, { workspaceId }) => { + // TODO: use `useUpdateWorkspaceInCache` after respond Workspace object, see EXP-960 const queryKey = getListWorkspacesQueryKey(org.data?.id); // Update workspace.pinned to account for the toggle so it's reflected immediately diff --git a/components/dashboard/src/data/workspaces/toggle-workspace-shared-mutation.ts b/components/dashboard/src/data/workspaces/toggle-workspace-shared-mutation.ts index 38b0acef8fb7cf..48346e7fcee1d3 100644 --- a/components/dashboard/src/data/workspaces/toggle-workspace-shared-mutation.ts +++ b/components/dashboard/src/data/workspaces/toggle-workspace-shared-mutation.ts @@ -33,6 +33,7 @@ export const useToggleWorkspaceSharedMutation = () => { if (level === AdmissionLevel.UNSPECIFIED) { return; } + // TODO: use `useUpdateWorkspaceInCache` after respond Workspace object, see EXP-960 const queryKey = getListWorkspacesQueryKey(org.data?.id); // Update workspace.shareable to the level we set so it's reflected immediately diff --git a/components/dashboard/src/data/workspaces/update-workspace-description-mutation.ts b/components/dashboard/src/data/workspaces/update-workspace-description-mutation.ts index a42033d413bf94..cd3c34c7d49dbf 100644 --- a/components/dashboard/src/data/workspaces/update-workspace-description-mutation.ts +++ b/components/dashboard/src/data/workspaces/update-workspace-description-mutation.ts @@ -23,6 +23,7 @@ export const useUpdateWorkspaceDescriptionMutation = () => { return await getGitpodService().server.setWorkspaceDescription(workspaceId, newDescription); }, onSuccess: (_, { workspaceId, newDescription }) => { + // TODO: use `useUpdateWorkspaceInCache` after respond Workspace object, see EXP-960 const queryKey = getListWorkspacesQueryKey(org.data?.id); // pro-actively update workspace description rather than reload all workspaces diff --git a/components/dashboard/src/service/json-rpc-workspace-client.ts b/components/dashboard/src/service/json-rpc-workspace-client.ts index 848727b594682b..242216cafee87d 100644 --- a/components/dashboard/src/service/json-rpc-workspace-client.ts +++ b/components/dashboard/src/service/json-rpc-workspace-client.ts @@ -8,8 +8,12 @@ import { CallOptions, PromiseClient } from "@connectrpc/connect"; import { PartialMessage } from "@bufbuild/protobuf"; import { WorkspaceService } from "@gitpod/public-api/lib/gitpod/v1/workspace_connect"; import { + CreateAndStartWorkspaceRequest, + CreateAndStartWorkspaceResponse, GetWorkspaceRequest, GetWorkspaceResponse, + StartWorkspaceRequest, + StartWorkspaceResponse, WatchWorkspaceStatusRequest, WatchWorkspaceStatusResponse, ListWorkspacesRequest, @@ -109,4 +113,50 @@ export class JsonRpcWorkspaceClient implements PromiseClient, + _options?: CallOptions | undefined, + ) { + if (request.source?.case !== "contextUrl") { + throw new ApplicationError(ErrorCodes.UNIMPLEMENTED, "not implemented"); + } + if (!request.organizationId || !uuidValidate(request.organizationId)) { + throw new ApplicationError(ErrorCodes.BAD_REQUEST, "organizationId is required"); + } + if (!request.editor) { + throw new ApplicationError(ErrorCodes.BAD_REQUEST, "editor is required"); + } + if (!request.source.value) { + throw new ApplicationError(ErrorCodes.BAD_REQUEST, "source is required"); + } + const response = await getGitpodService().server.createWorkspace({ + organizationId: request.organizationId, + ignoreRunningWorkspaceOnSameCommit: true, + contextUrl: request.source.value, + forceDefaultConfig: request.forceDefaultConfig, + workspaceClass: request.workspaceClass, + ideSettings: { + defaultIde: request.editor.name, + useLatestVersion: request.editor.version === "latest", + }, + }); + const workspace = await this.getWorkspace({ workspaceId: response.createdWorkspaceId }); + const result = new CreateAndStartWorkspaceResponse(); + result.workspace = workspace.workspace; + return result; + } + + async startWorkspace(request: PartialMessage, _options?: CallOptions | undefined) { + if (!request.workspaceId) { + throw new ApplicationError(ErrorCodes.BAD_REQUEST, "workspaceId is required"); + } + await getGitpodService().server.startWorkspace(request.workspaceId, { + forceDefaultImage: request.forceDefaultConfig, + }); + const workspace = await this.getWorkspace({ workspaceId: request.workspaceId }); + const result = new StartWorkspaceResponse(); + result.workspace = workspace.workspace; + return result; + } } diff --git a/components/dashboard/src/start/StartWorkspace.tsx b/components/dashboard/src/start/StartWorkspace.tsx index 1552a437e6c84d..e4ba2cbca73efb 100644 --- a/components/dashboard/src/start/StartWorkspace.tsx +++ b/components/dashboard/src/start/StartWorkspace.tsx @@ -4,13 +4,7 @@ * See License.AGPL.txt in the project root for license information. */ -import { - DisposableCollection, - GitpodServer, - RateLimiterError, - StartWorkspaceResult, - WorkspaceImageBuild, -} from "@gitpod/gitpod-protocol"; +import { DisposableCollection, RateLimiterError, WorkspaceImageBuild } from "@gitpod/gitpod-protocol"; import { IDEOptions } from "@gitpod/gitpod-protocol/lib/ide-protocol"; import { ErrorCodes } from "@gitpod/gitpod-protocol/lib/messaging/error"; import EventEmitter from "events"; @@ -26,9 +20,10 @@ import { StartPage, StartPhase, StartWorkspaceError } from "./StartPage"; import ConnectToSSHModal from "../workspaces/ConnectToSSHModal"; import Alert from "../components/Alert"; import { workspaceClient, workspacesService } from "../service/public-api"; -import { GetWorkspaceRequest, Workspace, WorkspacePhase_Phase } from "@gitpod/public-api/lib/gitpod/v1/workspace_pb"; 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 { PartialMessage } from "@bufbuild/protobuf"; const sessionId = v4(); @@ -97,6 +92,7 @@ export interface StartWorkspaceState { ownerToken?: string; } +// TODO: use Function Components export default class StartWorkspace extends React.Component { private ideFrontendService: IDEFrontendService | undefined; @@ -195,7 +191,7 @@ export default class StartWorkspace extends React.Component { + options: PartialMessage, + ): Promise { let retries = 0; while (true) { try { - return await getGitpodService().server.startWorkspace(workspaceId, options); + // TODO: use `useStartWorkspaceMutation` + return await workspaceClient.startWorkspace({ + ...options, + workspaceId, + }); } catch (err) { if (err?.code !== ErrorCodes.TOO_MANY_REQUESTS) { throw err; diff --git a/components/dashboard/src/workspaces/CreateWorkspacePage.tsx b/components/dashboard/src/workspaces/CreateWorkspacePage.tsx index 26b86eeded3ff5..25db0b676de797 100644 --- a/components/dashboard/src/workspaces/CreateWorkspacePage.tsx +++ b/components/dashboard/src/workspaces/CreateWorkspacePage.tsx @@ -4,13 +4,7 @@ * See License.AGPL.txt in the project root for license information. */ -import { - AdditionalUserData, - CommitContext, - GitpodServer, - SuggestedRepository, - WithReferrerContext, -} from "@gitpod/gitpod-protocol"; +import { AdditionalUserData, CommitContext, SuggestedRepository, WithReferrerContext } from "@gitpod/gitpod-protocol"; import { SelectAccountPayload } from "@gitpod/gitpod-protocol/lib/auth"; import { ErrorCodes } from "@gitpod/gitpod-protocol/lib/messaging/error"; import { Deferred } from "@gitpod/gitpod-protocol/lib/util/deferred"; @@ -46,6 +40,8 @@ import { AuthProviderType } from "@gitpod/public-api/lib/gitpod/v1/authprovider_ import { WorkspacePhase_Phase } from "@gitpod/public-api/lib/gitpod/v1/workspace_pb"; import { Button } from "@podkit/buttons/Button"; import { LoadingButton } from "@podkit/buttons/LoadingButton"; +import { CreateAndStartWorkspaceRequest } from "@gitpod/public-api/lib/gitpod/v1/workspace_pb"; +import { PartialMessage } from "@bufbuild/protobuf"; export function CreateWorkspacePage() { const { user, setUser } = useContext(UserContext); @@ -176,12 +172,20 @@ export function CreateWorkspacePage() { const [selectAccountError, setSelectAccountError] = useState(undefined); const createWorkspace = useCallback( - async (options?: Omit) => { + async (options?: Omit, "contextUrl" | "organizationId">) => { // add options from search params const opts = options || {}; - // we already have shown running workspaces to the user - opts.ignoreRunningWorkspaceOnSameCommit = true; + if (!contextURL) { + return; + } + + const organizationId = currentOrg?.id; + if (!organizationId) { + // We need an organizationId for this group of users + console.error("Skipping createWorkspace"); + return; + } // if user received an INVALID_GITPOD_YML yml for their contextURL they can choose to proceed using default configuration if (workspaceContext.error?.code === ErrorCodes.INVALID_GITPOD_YML) { @@ -191,22 +195,12 @@ export function CreateWorkspacePage() { if (!opts.workspaceClass) { opts.workspaceClass = selectedWsClass; } - if (!opts.ideSettings) { - opts.ideSettings = { - defaultIde: selectedIde, - useLatestVersion: useLatestIde, + if (!opts.editor) { + opts.editor = { + name: selectedIde, + version: useLatestIde ? "latest" : undefined, }; } - if (!contextURL) { - return; - } - - const organizationId = currentOrg?.id; - if (!organizationId) { - // We need an organizationId for this group of users - console.error("Skipping createWorkspace"); - return; - } try { if (createWorkspaceMutation.isStarting) { @@ -215,18 +209,22 @@ export function CreateWorkspacePage() { } // we wait at least 5 secs const timeout = new Promise((resolve) => setTimeout(resolve, 5000)); + const result = await createWorkspaceMutation.createWorkspace({ - contextUrl: contextURL, - organizationId, - projectId: selectedProjectID, + source: { + case: "contextUrl", + value: contextURL, + }, ...opts, + organizationId, + configurationId: selectedProjectID, }); await storeAutoStartOptions(); await timeout; - if (result.workspaceURL) { - window.location.href = result.workspaceURL; - } else if (result.createdWorkspaceId) { - history.push(`/start/#${result.createdWorkspaceId}`); + if (result.workspace?.status?.workspaceUrl) { + window.location.href = result.workspace.status.workspaceUrl; + } else if (result.workspace!.id) { + history.push(`/start/#${result.workspace!.id}`); } } catch (error) { console.log(error); diff --git a/components/gitpod-protocol/src/messaging/error.ts b/components/gitpod-protocol/src/messaging/error.ts index 863ffc1cb0e2a9..0ed66687f80cdd 100644 --- a/components/gitpod-protocol/src/messaging/error.ts +++ b/components/gitpod-protocol/src/messaging/error.ts @@ -144,6 +144,9 @@ export const ErrorCodes = { // 501 EE Feature EE_FEATURE: 501 as const, + // 521 Unimplemented + UNIMPLEMENTED: 521 as const, + // 555 EE License Required EE_LICENSE_REQUIRED: 555 as const, diff --git a/components/public-api/gitpod/v1/workspace.proto b/components/public-api/gitpod/v1/workspace.proto index db9e83d271d6db..e11bd0d7d292c9 100644 --- a/components/public-api/gitpod/v1/workspace.proto +++ b/components/public-api/gitpod/v1/workspace.proto @@ -14,13 +14,21 @@ service WorkspaceService { // ID +return NOT_FOUND Workspace does not exist rpc GetWorkspace(GetWorkspaceRequest) returns (GetWorkspaceResponse) {} - // WatchWorkspaceStatus watchs the workspaces status changes + // WatchWorkspaceStatus watches the workspaces status changes // // workspace_id +return NOT_FOUND Workspace does not exist rpc WatchWorkspaceStatus(WatchWorkspaceStatusRequest) returns (stream WatchWorkspaceStatusResponse) {} // ListWorkspaces returns a list of workspaces that match the query. rpc ListWorkspaces(ListWorkspacesRequest) returns (ListWorkspacesResponse) {} + + // CreateAndStartWorkspace creates a new workspace and starts it. + rpc CreateAndStartWorkspace(CreateAndStartWorkspaceRequest) + returns (CreateAndStartWorkspaceResponse) {} + + // 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) {} } message GetWorkspaceRequest { @@ -73,6 +81,81 @@ message ListWorkspacesResponse { PaginationResponse pagination = 2; } +message CreateAndStartWorkspaceRequest { + + message Git { + // clone_url is the URL of the repository to clone + string clone_url = 1; + + // ref is an alternatively symbolic. e.g. refs/tags/v1.0, + // empty string means the default branch of the repository + string ref = 2; + + // create_local_branch is the branch you want to create based on provided + // clone_url and ref when workspace started + string create_local_branch = 3; + } + + // organization_id is the ID of the organization to create the workspace + // + // +required + string organization_id = 1; + + // configuration_id is the ID of the configuration to use + string configuration_id = 2; + + // source describes the source refer of workspace. + // + // +required + oneof source { + + // git describes the source refer of workspace + // Obtain available git using the ContextService.ParseContext operation if + // not sure about it. + Git git = 3; + + // context_url is for backward compatiblity with the current dashboard, use + // ContextService.ParseContext get get a Git source instead + string context_url = 4 [ deprecated = true ]; + } + + // additional_env_variables provide additional environment variables to the + // workspace. + // It will take precedence over environment variables provided by + // the user and the configuration + repeated WorkspaceEnvironmentVariable additional_env_variables = 5; + + string region = 6; + + // workspace_class is the class of the workspace + + string workspace_class = 7; + + EditorReference editor = 8; + + string name = 9; + + bool pinned = 10; + + // force_default_config indicates that the workspace should be created with + // the default configuration instead of the configuration provided in + // `.gitpod.yml` file + bool force_default_config = 11; +} + +message CreateAndStartWorkspaceResponse { Workspace workspace = 1; } + +message StartWorkspaceRequest { + // workspace_id specifies the workspace that is going to start + // + // +required + string workspace_id = 1; + + bool force_default_config = 2; +} + +message StartWorkspaceResponse { Workspace workspace = 1; } + // +resource get workspace message Workspace { string id = 1; diff --git a/components/public-api/go/v1/context.pb.go b/components/public-api/go/v1/context.pb.go new file mode 100644 index 00000000000000..f5585f75096875 --- /dev/null +++ b/components/public-api/go/v1/context.pb.go @@ -0,0 +1,305 @@ +// Copyright (c) 2023 Gitpod GmbH. All rights reserved. +// Licensed under the GNU Affero General Public License (AGPL). +// See License.AGPL.txt in the project root for license information. + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.28.1 +// protoc (unknown) +// source: gitpod/v1/context.proto + +package v1 + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type ParseContextRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Url string `protobuf:"bytes,1,opt,name=url,proto3" json:"url,omitempty"` +} + +func (x *ParseContextRequest) Reset() { + *x = ParseContextRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_gitpod_v1_context_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ParseContextRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ParseContextRequest) ProtoMessage() {} + +func (x *ParseContextRequest) ProtoReflect() protoreflect.Message { + mi := &file_gitpod_v1_context_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ParseContextRequest.ProtoReflect.Descriptor instead. +func (*ParseContextRequest) Descriptor() ([]byte, []int) { + return file_gitpod_v1_context_proto_rawDescGZIP(), []int{0} +} + +func (x *ParseContextRequest) GetUrl() string { + if x != nil { + return x.Url + } + return "" +} + +type ParseContextResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Context *Context `protobuf:"bytes,1,opt,name=context,proto3" json:"context,omitempty"` +} + +func (x *ParseContextResponse) Reset() { + *x = ParseContextResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_gitpod_v1_context_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ParseContextResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ParseContextResponse) ProtoMessage() {} + +func (x *ParseContextResponse) ProtoReflect() protoreflect.Message { + mi := &file_gitpod_v1_context_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ParseContextResponse.ProtoReflect.Descriptor instead. +func (*ParseContextResponse) Descriptor() ([]byte, []int) { + return file_gitpod_v1_context_proto_rawDescGZIP(), []int{1} +} + +func (x *ParseContextResponse) GetContext() *Context { + if x != nil { + return x.Context + } + return nil +} + +type Context struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Title string `protobuf:"bytes,1,opt,name=title,proto3" json:"title,omitempty"` + NormalizedUrl string `protobuf:"bytes,2,opt,name=normalized_url,json=normalizedUrl,proto3" json:"normalized_url,omitempty"` + Ref string `protobuf:"bytes,3,opt,name=ref,proto3" json:"ref,omitempty"` +} + +func (x *Context) Reset() { + *x = Context{} + if protoimpl.UnsafeEnabled { + mi := &file_gitpod_v1_context_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Context) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Context) ProtoMessage() {} + +func (x *Context) ProtoReflect() protoreflect.Message { + mi := &file_gitpod_v1_context_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Context.ProtoReflect.Descriptor instead. +func (*Context) Descriptor() ([]byte, []int) { + return file_gitpod_v1_context_proto_rawDescGZIP(), []int{2} +} + +func (x *Context) GetTitle() string { + if x != nil { + return x.Title + } + return "" +} + +func (x *Context) GetNormalizedUrl() string { + if x != nil { + return x.NormalizedUrl + } + return "" +} + +func (x *Context) GetRef() string { + if x != nil { + return x.Ref + } + return "" +} + +var File_gitpod_v1_context_proto protoreflect.FileDescriptor + +var file_gitpod_v1_context_proto_rawDesc = []byte{ + 0x0a, 0x17, 0x67, 0x69, 0x74, 0x70, 0x6f, 0x64, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x6f, 0x6e, 0x74, + 0x65, 0x78, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x09, 0x67, 0x69, 0x74, 0x70, 0x6f, + 0x64, 0x2e, 0x76, 0x31, 0x22, 0x27, 0x0a, 0x13, 0x50, 0x61, 0x72, 0x73, 0x65, 0x43, 0x6f, 0x6e, + 0x74, 0x65, 0x78, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x75, + 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x22, 0x44, 0x0a, + 0x14, 0x50, 0x61, 0x72, 0x73, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2c, 0x0a, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x67, 0x69, 0x74, 0x70, 0x6f, 0x64, 0x2e, + 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x52, 0x07, 0x63, 0x6f, 0x6e, 0x74, + 0x65, 0x78, 0x74, 0x22, 0x58, 0x0a, 0x07, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x12, 0x14, + 0x0a, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, + 0x69, 0x74, 0x6c, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x69, 0x7a, + 0x65, 0x64, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x6f, + 0x72, 0x6d, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x55, 0x72, 0x6c, 0x12, 0x10, 0x0a, 0x03, 0x72, + 0x65, 0x66, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x72, 0x65, 0x66, 0x32, 0x63, 0x0a, + 0x0e, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, + 0x51, 0x0a, 0x0c, 0x50, 0x61, 0x72, 0x73, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x12, + 0x1e, 0x2e, 0x67, 0x69, 0x74, 0x70, 0x6f, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x61, 0x72, 0x73, + 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x1f, 0x2e, 0x67, 0x69, 0x74, 0x70, 0x6f, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x61, 0x72, 0x73, + 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x22, 0x00, 0x42, 0x39, 0x5a, 0x37, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, + 0x2f, 0x67, 0x69, 0x74, 0x70, 0x6f, 0x64, 0x2d, 0x69, 0x6f, 0x2f, 0x67, 0x69, 0x74, 0x70, 0x6f, + 0x64, 0x2f, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x73, 0x2f, 0x70, 0x75, 0x62, + 0x6c, 0x69, 0x63, 0x2d, 0x61, 0x70, 0x69, 0x2f, 0x67, 0x6f, 0x2f, 0x76, 0x31, 0x62, 0x06, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_gitpod_v1_context_proto_rawDescOnce sync.Once + file_gitpod_v1_context_proto_rawDescData = file_gitpod_v1_context_proto_rawDesc +) + +func file_gitpod_v1_context_proto_rawDescGZIP() []byte { + file_gitpod_v1_context_proto_rawDescOnce.Do(func() { + file_gitpod_v1_context_proto_rawDescData = protoimpl.X.CompressGZIP(file_gitpod_v1_context_proto_rawDescData) + }) + return file_gitpod_v1_context_proto_rawDescData +} + +var file_gitpod_v1_context_proto_msgTypes = make([]protoimpl.MessageInfo, 3) +var file_gitpod_v1_context_proto_goTypes = []interface{}{ + (*ParseContextRequest)(nil), // 0: gitpod.v1.ParseContextRequest + (*ParseContextResponse)(nil), // 1: gitpod.v1.ParseContextResponse + (*Context)(nil), // 2: gitpod.v1.Context +} +var file_gitpod_v1_context_proto_depIdxs = []int32{ + 2, // 0: gitpod.v1.ParseContextResponse.context:type_name -> gitpod.v1.Context + 0, // 1: gitpod.v1.ContextService.ParseContext:input_type -> gitpod.v1.ParseContextRequest + 1, // 2: gitpod.v1.ContextService.ParseContext:output_type -> gitpod.v1.ParseContextResponse + 2, // [2:3] is the sub-list for method output_type + 1, // [1:2] is the sub-list for method input_type + 1, // [1:1] is the sub-list for extension type_name + 1, // [1:1] is the sub-list for extension extendee + 0, // [0:1] is the sub-list for field type_name +} + +func init() { file_gitpod_v1_context_proto_init() } +func file_gitpod_v1_context_proto_init() { + if File_gitpod_v1_context_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_gitpod_v1_context_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ParseContextRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_gitpod_v1_context_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ParseContextResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_gitpod_v1_context_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Context); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_gitpod_v1_context_proto_rawDesc, + NumEnums: 0, + NumMessages: 3, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_gitpod_v1_context_proto_goTypes, + DependencyIndexes: file_gitpod_v1_context_proto_depIdxs, + MessageInfos: file_gitpod_v1_context_proto_msgTypes, + }.Build() + File_gitpod_v1_context_proto = out.File + file_gitpod_v1_context_proto_rawDesc = nil + file_gitpod_v1_context_proto_goTypes = nil + file_gitpod_v1_context_proto_depIdxs = nil +} diff --git a/components/public-api/go/v1/context_grpc.pb.go b/components/public-api/go/v1/context_grpc.pb.go new file mode 100644 index 00000000000000..d0783796bcdacd --- /dev/null +++ b/components/public-api/go/v1/context_grpc.pb.go @@ -0,0 +1,111 @@ +// Copyright (c) 2023 Gitpod GmbH. All rights reserved. +// Licensed under the GNU Affero General Public License (AGPL). +// See License.AGPL.txt in the project root for license information. + +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.2.0 +// - protoc (unknown) +// source: gitpod/v1/context.proto + +package v1 + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +// ContextServiceClient is the client API for ContextService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type ContextServiceClient interface { + // ParseContext parses the url and returns the context + ParseContext(ctx context.Context, in *ParseContextRequest, opts ...grpc.CallOption) (*ParseContextResponse, error) +} + +type contextServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewContextServiceClient(cc grpc.ClientConnInterface) ContextServiceClient { + return &contextServiceClient{cc} +} + +func (c *contextServiceClient) ParseContext(ctx context.Context, in *ParseContextRequest, opts ...grpc.CallOption) (*ParseContextResponse, error) { + out := new(ParseContextResponse) + err := c.cc.Invoke(ctx, "/gitpod.v1.ContextService/ParseContext", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// ContextServiceServer is the server API for ContextService service. +// All implementations must embed UnimplementedContextServiceServer +// for forward compatibility +type ContextServiceServer interface { + // ParseContext parses the url and returns the context + ParseContext(context.Context, *ParseContextRequest) (*ParseContextResponse, error) + mustEmbedUnimplementedContextServiceServer() +} + +// UnimplementedContextServiceServer must be embedded to have forward compatible implementations. +type UnimplementedContextServiceServer struct { +} + +func (UnimplementedContextServiceServer) ParseContext(context.Context, *ParseContextRequest) (*ParseContextResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ParseContext not implemented") +} +func (UnimplementedContextServiceServer) mustEmbedUnimplementedContextServiceServer() {} + +// UnsafeContextServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to ContextServiceServer will +// result in compilation errors. +type UnsafeContextServiceServer interface { + mustEmbedUnimplementedContextServiceServer() +} + +func RegisterContextServiceServer(s grpc.ServiceRegistrar, srv ContextServiceServer) { + s.RegisterService(&ContextService_ServiceDesc, srv) +} + +func _ContextService_ParseContext_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ParseContextRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ContextServiceServer).ParseContext(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/gitpod.v1.ContextService/ParseContext", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ContextServiceServer).ParseContext(ctx, req.(*ParseContextRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// ContextService_ServiceDesc is the grpc.ServiceDesc for ContextService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var ContextService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "gitpod.v1.ContextService", + HandlerType: (*ContextServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "ParseContext", + Handler: _ContextService_ParseContext_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "gitpod/v1/context.proto", +} diff --git a/components/public-api/go/v1/v1connect/context.connect.go b/components/public-api/go/v1/v1connect/context.connect.go new file mode 100644 index 00000000000000..4222b534bb562f --- /dev/null +++ b/components/public-api/go/v1/v1connect/context.connect.go @@ -0,0 +1,92 @@ +// Copyright (c) 2023 Gitpod GmbH. All rights reserved. +// Licensed under the GNU Affero General Public License (AGPL). +// See License.AGPL.txt in the project root for license information. + +// Code generated by protoc-gen-connect-go. DO NOT EDIT. +// +// Source: gitpod/v1/context.proto + +package v1connect + +import ( + context "context" + errors "errors" + connect_go "github.com/bufbuild/connect-go" + v1 "github.com/gitpod-io/gitpod/components/public-api/go/v1" + http "net/http" + strings "strings" +) + +// This is a compile-time assertion to ensure that this generated file and the connect package are +// compatible. If you get a compiler error that this constant is not defined, this code was +// generated with a version of connect newer than the one compiled into your binary. You can fix the +// problem by either regenerating this code with an older version of connect or updating the connect +// version compiled into your binary. +const _ = connect_go.IsAtLeastVersion0_1_0 + +const ( + // ContextServiceName is the fully-qualified name of the ContextService service. + ContextServiceName = "gitpod.v1.ContextService" +) + +// ContextServiceClient is a client for the gitpod.v1.ContextService service. +type ContextServiceClient interface { + // ParseContext parses the url and returns the context + ParseContext(context.Context, *connect_go.Request[v1.ParseContextRequest]) (*connect_go.Response[v1.ParseContextResponse], error) +} + +// NewContextServiceClient constructs a client for the gitpod.v1.ContextService service. By default, +// it uses the Connect protocol with the binary Protobuf Codec, asks for gzipped responses, and +// sends uncompressed requests. To use the gRPC or gRPC-Web protocols, supply the connect.WithGRPC() +// or connect.WithGRPCWeb() options. +// +// The URL supplied here should be the base URL for the Connect or gRPC server (for example, +// http://api.acme.com or https://acme.com/grpc). +func NewContextServiceClient(httpClient connect_go.HTTPClient, baseURL string, opts ...connect_go.ClientOption) ContextServiceClient { + baseURL = strings.TrimRight(baseURL, "/") + return &contextServiceClient{ + parseContext: connect_go.NewClient[v1.ParseContextRequest, v1.ParseContextResponse]( + httpClient, + baseURL+"/gitpod.v1.ContextService/ParseContext", + opts..., + ), + } +} + +// contextServiceClient implements ContextServiceClient. +type contextServiceClient struct { + parseContext *connect_go.Client[v1.ParseContextRequest, v1.ParseContextResponse] +} + +// ParseContext calls gitpod.v1.ContextService.ParseContext. +func (c *contextServiceClient) ParseContext(ctx context.Context, req *connect_go.Request[v1.ParseContextRequest]) (*connect_go.Response[v1.ParseContextResponse], error) { + return c.parseContext.CallUnary(ctx, req) +} + +// ContextServiceHandler is an implementation of the gitpod.v1.ContextService service. +type ContextServiceHandler interface { + // ParseContext parses the url and returns the context + ParseContext(context.Context, *connect_go.Request[v1.ParseContextRequest]) (*connect_go.Response[v1.ParseContextResponse], error) +} + +// NewContextServiceHandler builds an HTTP handler from the service implementation. It returns the +// path on which to mount the handler and the handler itself. +// +// By default, handlers support the Connect, gRPC, and gRPC-Web protocols with the binary Protobuf +// and JSON codecs. They also support gzip compression. +func NewContextServiceHandler(svc ContextServiceHandler, opts ...connect_go.HandlerOption) (string, http.Handler) { + mux := http.NewServeMux() + mux.Handle("/gitpod.v1.ContextService/ParseContext", connect_go.NewUnaryHandler( + "/gitpod.v1.ContextService/ParseContext", + svc.ParseContext, + opts..., + )) + return "/gitpod.v1.ContextService/", mux +} + +// UnimplementedContextServiceHandler returns CodeUnimplemented from all methods. +type UnimplementedContextServiceHandler struct{} + +func (UnimplementedContextServiceHandler) ParseContext(context.Context, *connect_go.Request[v1.ParseContextRequest]) (*connect_go.Response[v1.ParseContextResponse], error) { + return nil, connect_go.NewError(connect_go.CodeUnimplemented, errors.New("gitpod.v1.ContextService.ParseContext is not implemented")) +} diff --git a/components/public-api/go/v1/v1connect/context.proxy.connect.go b/components/public-api/go/v1/v1connect/context.proxy.connect.go new file mode 100644 index 00000000000000..6c22c915d0153e --- /dev/null +++ b/components/public-api/go/v1/v1connect/context.proxy.connect.go @@ -0,0 +1,30 @@ +// Copyright (c) 2023 Gitpod GmbH. All rights reserved. +// Licensed under the GNU Affero General Public License (AGPL). +// See License.AGPL.txt in the project root for license information. + +// Code generated by protoc-proxy-gen. DO NOT EDIT. + +package v1connect + +import ( + context "context" + connect_go "github.com/bufbuild/connect-go" + v1 "github.com/gitpod-io/gitpod/components/public-api/go/v1" +) + +var _ ContextServiceHandler = (*ProxyContextServiceHandler)(nil) + +type ProxyContextServiceHandler struct { + Client v1.ContextServiceClient + UnimplementedContextServiceHandler +} + +func (s *ProxyContextServiceHandler) ParseContext(ctx context.Context, req *connect_go.Request[v1.ParseContextRequest]) (*connect_go.Response[v1.ParseContextResponse], error) { + resp, err := s.Client.ParseContext(ctx, req.Msg) + if err != nil { + // TODO(milan): Convert to correct status code + return nil, err + } + + return connect_go.NewResponse(resp), nil +} diff --git a/components/public-api/go/v1/v1connect/workspace.connect.go b/components/public-api/go/v1/v1connect/workspace.connect.go index d13775cef2a7cd..691ab0d6497e7d 100644 --- a/components/public-api/go/v1/v1connect/workspace.connect.go +++ b/components/public-api/go/v1/v1connect/workspace.connect.go @@ -36,12 +36,17 @@ type WorkspaceServiceClient interface { // +return NOT_FOUND User does not have access to a workspace with the given // ID +return NOT_FOUND Workspace does not exist GetWorkspace(context.Context, *connect_go.Request[v1.GetWorkspaceRequest]) (*connect_go.Response[v1.GetWorkspaceResponse], error) - // WatchWorkspaceStatus watchs the workspaces status changes + // WatchWorkspaceStatus watches the workspaces status changes // // workspace_id +return NOT_FOUND Workspace does not exist WatchWorkspaceStatus(context.Context, *connect_go.Request[v1.WatchWorkspaceStatusRequest]) (*connect_go.ServerStreamForClient[v1.WatchWorkspaceStatusResponse], error) // ListWorkspaces returns a list of workspaces that match the query. ListWorkspaces(context.Context, *connect_go.Request[v1.ListWorkspacesRequest]) (*connect_go.Response[v1.ListWorkspacesResponse], error) + // CreateAndStartWorkspace creates a new workspace and starts it. + CreateAndStartWorkspace(context.Context, *connect_go.Request[v1.CreateAndStartWorkspaceRequest]) (*connect_go.Response[v1.CreateAndStartWorkspaceResponse], error) + // StartWorkspace starts an existing workspace. + // If the specified workspace is not in stopped phase, this will return the workspace as is. + StartWorkspace(context.Context, *connect_go.Request[v1.StartWorkspaceRequest]) (*connect_go.Response[v1.StartWorkspaceResponse], error) } // NewWorkspaceServiceClient constructs a client for the gitpod.v1.WorkspaceService service. By @@ -69,14 +74,26 @@ func NewWorkspaceServiceClient(httpClient connect_go.HTTPClient, baseURL string, baseURL+"/gitpod.v1.WorkspaceService/ListWorkspaces", opts..., ), + createAndStartWorkspace: connect_go.NewClient[v1.CreateAndStartWorkspaceRequest, v1.CreateAndStartWorkspaceResponse]( + httpClient, + baseURL+"/gitpod.v1.WorkspaceService/CreateAndStartWorkspace", + opts..., + ), + startWorkspace: connect_go.NewClient[v1.StartWorkspaceRequest, v1.StartWorkspaceResponse]( + httpClient, + baseURL+"/gitpod.v1.WorkspaceService/StartWorkspace", + opts..., + ), } } // workspaceServiceClient implements WorkspaceServiceClient. type workspaceServiceClient struct { - getWorkspace *connect_go.Client[v1.GetWorkspaceRequest, v1.GetWorkspaceResponse] - watchWorkspaceStatus *connect_go.Client[v1.WatchWorkspaceStatusRequest, v1.WatchWorkspaceStatusResponse] - listWorkspaces *connect_go.Client[v1.ListWorkspacesRequest, v1.ListWorkspacesResponse] + getWorkspace *connect_go.Client[v1.GetWorkspaceRequest, v1.GetWorkspaceResponse] + watchWorkspaceStatus *connect_go.Client[v1.WatchWorkspaceStatusRequest, v1.WatchWorkspaceStatusResponse] + listWorkspaces *connect_go.Client[v1.ListWorkspacesRequest, v1.ListWorkspacesResponse] + createAndStartWorkspace *connect_go.Client[v1.CreateAndStartWorkspaceRequest, v1.CreateAndStartWorkspaceResponse] + startWorkspace *connect_go.Client[v1.StartWorkspaceRequest, v1.StartWorkspaceResponse] } // GetWorkspace calls gitpod.v1.WorkspaceService.GetWorkspace. @@ -94,6 +111,16 @@ func (c *workspaceServiceClient) ListWorkspaces(ctx context.Context, req *connec return c.listWorkspaces.CallUnary(ctx, req) } +// CreateAndStartWorkspace calls gitpod.v1.WorkspaceService.CreateAndStartWorkspace. +func (c *workspaceServiceClient) CreateAndStartWorkspace(ctx context.Context, req *connect_go.Request[v1.CreateAndStartWorkspaceRequest]) (*connect_go.Response[v1.CreateAndStartWorkspaceResponse], error) { + return c.createAndStartWorkspace.CallUnary(ctx, req) +} + +// StartWorkspace calls gitpod.v1.WorkspaceService.StartWorkspace. +func (c *workspaceServiceClient) StartWorkspace(ctx context.Context, req *connect_go.Request[v1.StartWorkspaceRequest]) (*connect_go.Response[v1.StartWorkspaceResponse], error) { + return c.startWorkspace.CallUnary(ctx, req) +} + // WorkspaceServiceHandler is an implementation of the gitpod.v1.WorkspaceService service. type WorkspaceServiceHandler interface { // GetWorkspace returns a single workspace. @@ -101,12 +128,17 @@ type WorkspaceServiceHandler interface { // +return NOT_FOUND User does not have access to a workspace with the given // ID +return NOT_FOUND Workspace does not exist GetWorkspace(context.Context, *connect_go.Request[v1.GetWorkspaceRequest]) (*connect_go.Response[v1.GetWorkspaceResponse], error) - // WatchWorkspaceStatus watchs the workspaces status changes + // WatchWorkspaceStatus watches the workspaces status changes // // workspace_id +return NOT_FOUND Workspace does not exist WatchWorkspaceStatus(context.Context, *connect_go.Request[v1.WatchWorkspaceStatusRequest], *connect_go.ServerStream[v1.WatchWorkspaceStatusResponse]) error // ListWorkspaces returns a list of workspaces that match the query. ListWorkspaces(context.Context, *connect_go.Request[v1.ListWorkspacesRequest]) (*connect_go.Response[v1.ListWorkspacesResponse], error) + // CreateAndStartWorkspace creates a new workspace and starts it. + CreateAndStartWorkspace(context.Context, *connect_go.Request[v1.CreateAndStartWorkspaceRequest]) (*connect_go.Response[v1.CreateAndStartWorkspaceResponse], error) + // StartWorkspace starts an existing workspace. + // If the specified workspace is not in stopped phase, this will return the workspace as is. + StartWorkspace(context.Context, *connect_go.Request[v1.StartWorkspaceRequest]) (*connect_go.Response[v1.StartWorkspaceResponse], error) } // NewWorkspaceServiceHandler builds an HTTP handler from the service implementation. It returns the @@ -131,6 +163,16 @@ func NewWorkspaceServiceHandler(svc WorkspaceServiceHandler, opts ...connect_go. svc.ListWorkspaces, opts..., )) + mux.Handle("/gitpod.v1.WorkspaceService/CreateAndStartWorkspace", connect_go.NewUnaryHandler( + "/gitpod.v1.WorkspaceService/CreateAndStartWorkspace", + svc.CreateAndStartWorkspace, + opts..., + )) + mux.Handle("/gitpod.v1.WorkspaceService/StartWorkspace", connect_go.NewUnaryHandler( + "/gitpod.v1.WorkspaceService/StartWorkspace", + svc.StartWorkspace, + opts..., + )) return "/gitpod.v1.WorkspaceService/", mux } @@ -148,3 +190,11 @@ func (UnimplementedWorkspaceServiceHandler) WatchWorkspaceStatus(context.Context func (UnimplementedWorkspaceServiceHandler) ListWorkspaces(context.Context, *connect_go.Request[v1.ListWorkspacesRequest]) (*connect_go.Response[v1.ListWorkspacesResponse], error) { return nil, connect_go.NewError(connect_go.CodeUnimplemented, errors.New("gitpod.v1.WorkspaceService.ListWorkspaces is not implemented")) } + +func (UnimplementedWorkspaceServiceHandler) CreateAndStartWorkspace(context.Context, *connect_go.Request[v1.CreateAndStartWorkspaceRequest]) (*connect_go.Response[v1.CreateAndStartWorkspaceResponse], error) { + return nil, connect_go.NewError(connect_go.CodeUnimplemented, errors.New("gitpod.v1.WorkspaceService.CreateAndStartWorkspace is not implemented")) +} + +func (UnimplementedWorkspaceServiceHandler) StartWorkspace(context.Context, *connect_go.Request[v1.StartWorkspaceRequest]) (*connect_go.Response[v1.StartWorkspaceResponse], error) { + return nil, connect_go.NewError(connect_go.CodeUnimplemented, errors.New("gitpod.v1.WorkspaceService.StartWorkspace is not implemented")) +} diff --git a/components/public-api/go/v1/v1connect/workspace.proxy.connect.go b/components/public-api/go/v1/v1connect/workspace.proxy.connect.go index 5488fe53bc1910..0c5e4c6064d04a 100644 --- a/components/public-api/go/v1/v1connect/workspace.proxy.connect.go +++ b/components/public-api/go/v1/v1connect/workspace.proxy.connect.go @@ -38,3 +38,23 @@ func (s *ProxyWorkspaceServiceHandler) ListWorkspaces(ctx context.Context, req * return connect_go.NewResponse(resp), nil } + +func (s *ProxyWorkspaceServiceHandler) CreateAndStartWorkspace(ctx context.Context, req *connect_go.Request[v1.CreateAndStartWorkspaceRequest]) (*connect_go.Response[v1.CreateAndStartWorkspaceResponse], error) { + resp, err := s.Client.CreateAndStartWorkspace(ctx, req.Msg) + if err != nil { + // TODO(milan): Convert to correct status code + return nil, err + } + + return connect_go.NewResponse(resp), nil +} + +func (s *ProxyWorkspaceServiceHandler) StartWorkspace(ctx context.Context, req *connect_go.Request[v1.StartWorkspaceRequest]) (*connect_go.Response[v1.StartWorkspaceResponse], error) { + resp, err := s.Client.StartWorkspace(ctx, req.Msg) + if err != nil { + // TODO(milan): Convert to correct status code + return nil, err + } + + return connect_go.NewResponse(resp), nil +} diff --git a/components/public-api/go/v1/workspace.pb.go b/components/public-api/go/v1/workspace.pb.go index 8da72b16cde5e5..c04a27d59bc8ae 100644 --- a/components/public-api/go/v1/workspace.pb.go +++ b/components/public-api/go/v1/workspace.pb.go @@ -131,7 +131,7 @@ func (x WorkspacePort_Policy) Number() protoreflect.EnumNumber { // Deprecated: Use WorkspacePort_Policy.Descriptor instead. func (WorkspacePort_Policy) EnumDescriptor() ([]byte, []int) { - return file_gitpod_v1_workspace_proto_rawDescGZIP(), []int{9, 0} + return file_gitpod_v1_workspace_proto_rawDescGZIP(), []int{13, 0} } // Protocol defines the backend protocol of port @@ -183,7 +183,7 @@ func (x WorkspacePort_Protocol) Number() protoreflect.EnumNumber { // Deprecated: Use WorkspacePort_Protocol.Descriptor instead. func (WorkspacePort_Protocol) EnumDescriptor() ([]byte, []int) { - return file_gitpod_v1_workspace_proto_rawDescGZIP(), []int{9, 1} + return file_gitpod_v1_workspace_proto_rawDescGZIP(), []int{13, 1} } type WorkspacePhase_Phase int32 @@ -280,7 +280,7 @@ func (x WorkspacePhase_Phase) Number() protoreflect.EnumNumber { // Deprecated: Use WorkspacePhase_Phase.Descriptor instead. func (WorkspacePhase_Phase) EnumDescriptor() ([]byte, []int) { - return file_gitpod_v1_workspace_proto_rawDescGZIP(), []int{11, 0} + return file_gitpod_v1_workspace_proto_rawDescGZIP(), []int{15, 0} } type GetWorkspaceRequest struct { @@ -621,6 +621,334 @@ func (x *ListWorkspacesResponse) GetPagination() *PaginationResponse { return nil } +type CreateAndStartWorkspaceRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // organization_id is the ID of the organization to create the workspace + // + // +required + OrganizationId string `protobuf:"bytes,1,opt,name=organization_id,json=organizationId,proto3" json:"organization_id,omitempty"` + // configuration_id is the ID of the configuration to use + ConfigurationId string `protobuf:"bytes,2,opt,name=configuration_id,json=configurationId,proto3" json:"configuration_id,omitempty"` + // source describes the source refer of workspace. + // + // +required + // + // Types that are assignable to Source: + // + // *CreateAndStartWorkspaceRequest_Git_ + // *CreateAndStartWorkspaceRequest_ContextUrl + Source isCreateAndStartWorkspaceRequest_Source `protobuf_oneof:"source"` + // additional_env_variables provide additional environment variables to the + // workspace. + // It will take precedence over environment variables provided by + // the user and the configuration + AdditionalEnvVariables []*WorkspaceEnvironmentVariable `protobuf:"bytes,5,rep,name=additional_env_variables,json=additionalEnvVariables,proto3" json:"additional_env_variables,omitempty"` + Region string `protobuf:"bytes,6,opt,name=region,proto3" json:"region,omitempty"` + WorkspaceClass string `protobuf:"bytes,7,opt,name=workspace_class,json=workspaceClass,proto3" json:"workspace_class,omitempty"` + Editor *EditorReference `protobuf:"bytes,8,opt,name=editor,proto3" json:"editor,omitempty"` + Name string `protobuf:"bytes,9,opt,name=name,proto3" json:"name,omitempty"` + Pinned bool `protobuf:"varint,10,opt,name=pinned,proto3" json:"pinned,omitempty"` + // force_default_config indicates that the workspace should be created with + // the default configuration instead of the configuration provided in + // `.gitpod.yml` file + ForceDefaultConfig bool `protobuf:"varint,11,opt,name=force_default_config,json=forceDefaultConfig,proto3" json:"force_default_config,omitempty"` +} + +func (x *CreateAndStartWorkspaceRequest) Reset() { + *x = CreateAndStartWorkspaceRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_gitpod_v1_workspace_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CreateAndStartWorkspaceRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateAndStartWorkspaceRequest) ProtoMessage() {} + +func (x *CreateAndStartWorkspaceRequest) ProtoReflect() protoreflect.Message { + mi := &file_gitpod_v1_workspace_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateAndStartWorkspaceRequest.ProtoReflect.Descriptor instead. +func (*CreateAndStartWorkspaceRequest) Descriptor() ([]byte, []int) { + return file_gitpod_v1_workspace_proto_rawDescGZIP(), []int{6} +} + +func (x *CreateAndStartWorkspaceRequest) GetOrganizationId() string { + if x != nil { + return x.OrganizationId + } + return "" +} + +func (x *CreateAndStartWorkspaceRequest) GetConfigurationId() string { + if x != nil { + return x.ConfigurationId + } + return "" +} + +func (m *CreateAndStartWorkspaceRequest) GetSource() isCreateAndStartWorkspaceRequest_Source { + if m != nil { + return m.Source + } + return nil +} + +func (x *CreateAndStartWorkspaceRequest) GetGit() *CreateAndStartWorkspaceRequest_Git { + if x, ok := x.GetSource().(*CreateAndStartWorkspaceRequest_Git_); ok { + return x.Git + } + return nil +} + +// Deprecated: Do not use. +func (x *CreateAndStartWorkspaceRequest) GetContextUrl() string { + if x, ok := x.GetSource().(*CreateAndStartWorkspaceRequest_ContextUrl); ok { + return x.ContextUrl + } + return "" +} + +func (x *CreateAndStartWorkspaceRequest) GetAdditionalEnvVariables() []*WorkspaceEnvironmentVariable { + if x != nil { + return x.AdditionalEnvVariables + } + return nil +} + +func (x *CreateAndStartWorkspaceRequest) GetRegion() string { + if x != nil { + return x.Region + } + return "" +} + +func (x *CreateAndStartWorkspaceRequest) GetWorkspaceClass() string { + if x != nil { + return x.WorkspaceClass + } + return "" +} + +func (x *CreateAndStartWorkspaceRequest) GetEditor() *EditorReference { + if x != nil { + return x.Editor + } + return nil +} + +func (x *CreateAndStartWorkspaceRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *CreateAndStartWorkspaceRequest) GetPinned() bool { + if x != nil { + return x.Pinned + } + return false +} + +func (x *CreateAndStartWorkspaceRequest) GetForceDefaultConfig() bool { + if x != nil { + return x.ForceDefaultConfig + } + return false +} + +type isCreateAndStartWorkspaceRequest_Source interface { + isCreateAndStartWorkspaceRequest_Source() +} + +type CreateAndStartWorkspaceRequest_Git_ struct { + // git describes the source refer of workspace + // Obtain available git using the ContextService.ParseContext operation if + // not sure about it. + Git *CreateAndStartWorkspaceRequest_Git `protobuf:"bytes,3,opt,name=git,proto3,oneof"` +} + +type CreateAndStartWorkspaceRequest_ContextUrl struct { + // context_url is for backward compatiblity with the current dashboard, use + // ContextService.ParseContext get get a Git source instead + // + // Deprecated: Do not use. + ContextUrl string `protobuf:"bytes,4,opt,name=context_url,json=contextUrl,proto3,oneof"` +} + +func (*CreateAndStartWorkspaceRequest_Git_) isCreateAndStartWorkspaceRequest_Source() {} + +func (*CreateAndStartWorkspaceRequest_ContextUrl) isCreateAndStartWorkspaceRequest_Source() {} + +type CreateAndStartWorkspaceResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Workspace *Workspace `protobuf:"bytes,1,opt,name=workspace,proto3" json:"workspace,omitempty"` +} + +func (x *CreateAndStartWorkspaceResponse) Reset() { + *x = CreateAndStartWorkspaceResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_gitpod_v1_workspace_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CreateAndStartWorkspaceResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateAndStartWorkspaceResponse) ProtoMessage() {} + +func (x *CreateAndStartWorkspaceResponse) ProtoReflect() protoreflect.Message { + mi := &file_gitpod_v1_workspace_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateAndStartWorkspaceResponse.ProtoReflect.Descriptor instead. +func (*CreateAndStartWorkspaceResponse) Descriptor() ([]byte, []int) { + return file_gitpod_v1_workspace_proto_rawDescGZIP(), []int{7} +} + +func (x *CreateAndStartWorkspaceResponse) GetWorkspace() *Workspace { + if x != nil { + return x.Workspace + } + return nil +} + +type StartWorkspaceRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // workspace_id specifies the workspace that is going to start + // + // +required + WorkspaceId string `protobuf:"bytes,1,opt,name=workspace_id,json=workspaceId,proto3" json:"workspace_id,omitempty"` + ForceDefaultConfig bool `protobuf:"varint,2,opt,name=force_default_config,json=forceDefaultConfig,proto3" json:"force_default_config,omitempty"` +} + +func (x *StartWorkspaceRequest) Reset() { + *x = StartWorkspaceRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_gitpod_v1_workspace_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *StartWorkspaceRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StartWorkspaceRequest) ProtoMessage() {} + +func (x *StartWorkspaceRequest) ProtoReflect() protoreflect.Message { + mi := &file_gitpod_v1_workspace_proto_msgTypes[8] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StartWorkspaceRequest.ProtoReflect.Descriptor instead. +func (*StartWorkspaceRequest) Descriptor() ([]byte, []int) { + return file_gitpod_v1_workspace_proto_rawDescGZIP(), []int{8} +} + +func (x *StartWorkspaceRequest) GetWorkspaceId() string { + if x != nil { + return x.WorkspaceId + } + return "" +} + +func (x *StartWorkspaceRequest) GetForceDefaultConfig() bool { + if x != nil { + return x.ForceDefaultConfig + } + return false +} + +type StartWorkspaceResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Workspace *Workspace `protobuf:"bytes,1,opt,name=workspace,proto3" json:"workspace,omitempty"` +} + +func (x *StartWorkspaceResponse) Reset() { + *x = StartWorkspaceResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_gitpod_v1_workspace_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *StartWorkspaceResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StartWorkspaceResponse) ProtoMessage() {} + +func (x *StartWorkspaceResponse) ProtoReflect() protoreflect.Message { + mi := &file_gitpod_v1_workspace_proto_msgTypes[9] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StartWorkspaceResponse.ProtoReflect.Descriptor instead. +func (*StartWorkspaceResponse) Descriptor() ([]byte, []int) { + return file_gitpod_v1_workspace_proto_rawDescGZIP(), []int{9} +} + +func (x *StartWorkspaceResponse) GetWorkspace() *Workspace { + if x != nil { + return x.Workspace + } + return nil +} + // +resource get workspace type Workspace struct { state protoimpl.MessageState @@ -670,7 +998,7 @@ type Workspace struct { func (x *Workspace) Reset() { *x = Workspace{} if protoimpl.UnsafeEnabled { - mi := &file_gitpod_v1_workspace_proto_msgTypes[6] + mi := &file_gitpod_v1_workspace_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -683,7 +1011,7 @@ func (x *Workspace) String() string { func (*Workspace) ProtoMessage() {} func (x *Workspace) ProtoReflect() protoreflect.Message { - mi := &file_gitpod_v1_workspace_proto_msgTypes[6] + mi := &file_gitpod_v1_workspace_proto_msgTypes[10] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -696,7 +1024,7 @@ func (x *Workspace) ProtoReflect() protoreflect.Message { // Deprecated: Use Workspace.ProtoReflect.Descriptor instead. func (*Workspace) Descriptor() ([]byte, []int) { - return file_gitpod_v1_workspace_proto_rawDescGZIP(), []int{6} + return file_gitpod_v1_workspace_proto_rawDescGZIP(), []int{10} } func (x *Workspace) GetId() string { @@ -815,7 +1143,7 @@ type WorkspaceStatus struct { func (x *WorkspaceStatus) Reset() { *x = WorkspaceStatus{} if protoimpl.UnsafeEnabled { - mi := &file_gitpod_v1_workspace_proto_msgTypes[7] + mi := &file_gitpod_v1_workspace_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -828,7 +1156,7 @@ func (x *WorkspaceStatus) String() string { func (*WorkspaceStatus) ProtoMessage() {} func (x *WorkspaceStatus) ProtoReflect() protoreflect.Message { - mi := &file_gitpod_v1_workspace_proto_msgTypes[7] + mi := &file_gitpod_v1_workspace_proto_msgTypes[11] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -841,7 +1169,7 @@ func (x *WorkspaceStatus) ProtoReflect() protoreflect.Message { // Deprecated: Use WorkspaceStatus.ProtoReflect.Descriptor instead. func (*WorkspaceStatus) Descriptor() ([]byte, []int) { - return file_gitpod_v1_workspace_proto_rawDescGZIP(), []int{7} + return file_gitpod_v1_workspace_proto_rawDescGZIP(), []int{11} } func (x *WorkspaceStatus) GetPhase() *WorkspacePhase { @@ -916,7 +1244,7 @@ type WorkspaceConditions struct { func (x *WorkspaceConditions) Reset() { *x = WorkspaceConditions{} if protoimpl.UnsafeEnabled { - mi := &file_gitpod_v1_workspace_proto_msgTypes[8] + mi := &file_gitpod_v1_workspace_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -929,7 +1257,7 @@ func (x *WorkspaceConditions) String() string { func (*WorkspaceConditions) ProtoMessage() {} func (x *WorkspaceConditions) ProtoReflect() protoreflect.Message { - mi := &file_gitpod_v1_workspace_proto_msgTypes[8] + mi := &file_gitpod_v1_workspace_proto_msgTypes[12] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -942,7 +1270,7 @@ func (x *WorkspaceConditions) ProtoReflect() protoreflect.Message { // Deprecated: Use WorkspaceConditions.ProtoReflect.Descriptor instead. func (*WorkspaceConditions) Descriptor() ([]byte, []int) { - return file_gitpod_v1_workspace_proto_rawDescGZIP(), []int{8} + return file_gitpod_v1_workspace_proto_rawDescGZIP(), []int{12} } func (x *WorkspaceConditions) GetFailed() string { @@ -977,7 +1305,7 @@ type WorkspacePort struct { func (x *WorkspacePort) Reset() { *x = WorkspacePort{} if protoimpl.UnsafeEnabled { - mi := &file_gitpod_v1_workspace_proto_msgTypes[9] + mi := &file_gitpod_v1_workspace_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -990,7 +1318,7 @@ func (x *WorkspacePort) String() string { func (*WorkspacePort) ProtoMessage() {} func (x *WorkspacePort) ProtoReflect() protoreflect.Message { - mi := &file_gitpod_v1_workspace_proto_msgTypes[9] + mi := &file_gitpod_v1_workspace_proto_msgTypes[13] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1003,7 +1331,7 @@ func (x *WorkspacePort) ProtoReflect() protoreflect.Message { // Deprecated: Use WorkspacePort.ProtoReflect.Descriptor instead. func (*WorkspacePort) Descriptor() ([]byte, []int) { - return file_gitpod_v1_workspace_proto_rawDescGZIP(), []int{9} + return file_gitpod_v1_workspace_proto_rawDescGZIP(), []int{13} } func (x *WorkspacePort) GetPort() uint64 { @@ -1065,7 +1393,7 @@ type WorkspaceGitStatus struct { func (x *WorkspaceGitStatus) Reset() { *x = WorkspaceGitStatus{} if protoimpl.UnsafeEnabled { - mi := &file_gitpod_v1_workspace_proto_msgTypes[10] + mi := &file_gitpod_v1_workspace_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1078,7 +1406,7 @@ func (x *WorkspaceGitStatus) String() string { func (*WorkspaceGitStatus) ProtoMessage() {} func (x *WorkspaceGitStatus) ProtoReflect() protoreflect.Message { - mi := &file_gitpod_v1_workspace_proto_msgTypes[10] + mi := &file_gitpod_v1_workspace_proto_msgTypes[14] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1091,7 +1419,7 @@ func (x *WorkspaceGitStatus) ProtoReflect() protoreflect.Message { // Deprecated: Use WorkspaceGitStatus.ProtoReflect.Descriptor instead. func (*WorkspaceGitStatus) Descriptor() ([]byte, []int) { - return file_gitpod_v1_workspace_proto_rawDescGZIP(), []int{10} + return file_gitpod_v1_workspace_proto_rawDescGZIP(), []int{14} } func (x *WorkspaceGitStatus) GetCloneUrl() string { @@ -1169,7 +1497,7 @@ type WorkspacePhase struct { func (x *WorkspacePhase) Reset() { *x = WorkspacePhase{} if protoimpl.UnsafeEnabled { - mi := &file_gitpod_v1_workspace_proto_msgTypes[11] + mi := &file_gitpod_v1_workspace_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1182,7 +1510,7 @@ func (x *WorkspacePhase) String() string { func (*WorkspacePhase) ProtoMessage() {} func (x *WorkspacePhase) ProtoReflect() protoreflect.Message { - mi := &file_gitpod_v1_workspace_proto_msgTypes[11] + mi := &file_gitpod_v1_workspace_proto_msgTypes[15] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1195,7 +1523,7 @@ func (x *WorkspacePhase) ProtoReflect() protoreflect.Message { // Deprecated: Use WorkspacePhase.ProtoReflect.Descriptor instead. func (*WorkspacePhase) Descriptor() ([]byte, []int) { - return file_gitpod_v1_workspace_proto_rawDescGZIP(), []int{11} + return file_gitpod_v1_workspace_proto_rawDescGZIP(), []int{15} } func (x *WorkspacePhase) GetName() WorkspacePhase_Phase { @@ -1224,7 +1552,7 @@ type EditorReference struct { func (x *EditorReference) Reset() { *x = EditorReference{} if protoimpl.UnsafeEnabled { - mi := &file_gitpod_v1_workspace_proto_msgTypes[12] + mi := &file_gitpod_v1_workspace_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1237,7 +1565,7 @@ func (x *EditorReference) String() string { func (*EditorReference) ProtoMessage() {} func (x *EditorReference) ProtoReflect() protoreflect.Message { - mi := &file_gitpod_v1_workspace_proto_msgTypes[12] + mi := &file_gitpod_v1_workspace_proto_msgTypes[16] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1250,7 +1578,7 @@ func (x *EditorReference) ProtoReflect() protoreflect.Message { // Deprecated: Use EditorReference.ProtoReflect.Descriptor instead. func (*EditorReference) Descriptor() ([]byte, []int) { - return file_gitpod_v1_workspace_proto_rawDescGZIP(), []int{12} + return file_gitpod_v1_workspace_proto_rawDescGZIP(), []int{16} } func (x *EditorReference) GetName() string { @@ -1279,7 +1607,7 @@ type WorkspaceEnvironmentVariable struct { func (x *WorkspaceEnvironmentVariable) Reset() { *x = WorkspaceEnvironmentVariable{} if protoimpl.UnsafeEnabled { - mi := &file_gitpod_v1_workspace_proto_msgTypes[13] + mi := &file_gitpod_v1_workspace_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1292,7 +1620,7 @@ func (x *WorkspaceEnvironmentVariable) String() string { func (*WorkspaceEnvironmentVariable) ProtoMessage() {} func (x *WorkspaceEnvironmentVariable) ProtoReflect() protoreflect.Message { - mi := &file_gitpod_v1_workspace_proto_msgTypes[13] + mi := &file_gitpod_v1_workspace_proto_msgTypes[17] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1305,7 +1633,7 @@ func (x *WorkspaceEnvironmentVariable) ProtoReflect() protoreflect.Message { // Deprecated: Use WorkspaceEnvironmentVariable.ProtoReflect.Descriptor instead. func (*WorkspaceEnvironmentVariable) Descriptor() ([]byte, []int) { - return file_gitpod_v1_workspace_proto_rawDescGZIP(), []int{13} + return file_gitpod_v1_workspace_proto_rawDescGZIP(), []int{17} } func (x *WorkspaceEnvironmentVariable) GetName() string { @@ -1322,6 +1650,74 @@ func (x *WorkspaceEnvironmentVariable) GetValue() string { return "" } +type CreateAndStartWorkspaceRequest_Git struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // clone_url is the URL of the repository to clone + CloneUrl string `protobuf:"bytes,1,opt,name=clone_url,json=cloneUrl,proto3" json:"clone_url,omitempty"` + // ref is an alternatively symbolic. e.g. refs/tags/v1.0, + // empty string means the default branch of the repository + Ref string `protobuf:"bytes,2,opt,name=ref,proto3" json:"ref,omitempty"` + // create_local_branch is the branch you want to create based on provided + // clone_url and ref when workspace started + CreateLocalBranch string `protobuf:"bytes,3,opt,name=create_local_branch,json=createLocalBranch,proto3" json:"create_local_branch,omitempty"` +} + +func (x *CreateAndStartWorkspaceRequest_Git) Reset() { + *x = CreateAndStartWorkspaceRequest_Git{} + if protoimpl.UnsafeEnabled { + mi := &file_gitpod_v1_workspace_proto_msgTypes[18] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CreateAndStartWorkspaceRequest_Git) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateAndStartWorkspaceRequest_Git) ProtoMessage() {} + +func (x *CreateAndStartWorkspaceRequest_Git) ProtoReflect() protoreflect.Message { + mi := &file_gitpod_v1_workspace_proto_msgTypes[18] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateAndStartWorkspaceRequest_Git.ProtoReflect.Descriptor instead. +func (*CreateAndStartWorkspaceRequest_Git) Descriptor() ([]byte, []int) { + return file_gitpod_v1_workspace_proto_rawDescGZIP(), []int{6, 0} +} + +func (x *CreateAndStartWorkspaceRequest_Git) GetCloneUrl() string { + if x != nil { + return x.CloneUrl + } + return "" +} + +func (x *CreateAndStartWorkspaceRequest_Git) GetRef() string { + if x != nil { + return x.Ref + } + return "" +} + +func (x *CreateAndStartWorkspaceRequest_Git) GetCreateLocalBranch() string { + if x != nil { + return x.CreateLocalBranch + } + return "" +} + var File_gitpod_v1_workspace_proto protoreflect.FileDescriptor var file_gitpod_v1_workspace_proto_rawDesc = []byte{ @@ -1371,175 +1767,245 @@ var file_gitpod_v1_workspace_proto_rawDesc = []byte{ 0x0a, 0x70, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x67, 0x69, 0x74, 0x70, 0x6f, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x52, 0x0a, 0x70, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0xea, 0x03, 0x0a, - 0x09, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, - 0x65, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x70, 0x72, - 0x65, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x12, 0x27, 0x0a, 0x0f, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, - 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0e, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, - 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, - 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x69, 0x6e, 0x6e, 0x65, 0x64, 0x18, 0x05, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x06, 0x70, 0x69, 0x6e, 0x6e, 0x65, 0x64, 0x12, 0x32, 0x0a, 0x06, 0x73, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x69, + 0x52, 0x0a, 0x70, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x84, 0x05, 0x0a, + 0x1e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x41, 0x6e, 0x64, 0x53, 0x74, 0x61, 0x72, 0x74, 0x57, + 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x27, 0x0a, 0x0f, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, + 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, + 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x29, 0x0a, 0x10, 0x63, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x49, 0x64, 0x12, 0x41, 0x0a, 0x03, 0x67, 0x69, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x2d, 0x2e, 0x67, 0x69, 0x74, 0x70, 0x6f, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, + 0x61, 0x74, 0x65, 0x41, 0x6e, 0x64, 0x53, 0x74, 0x61, 0x72, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x73, + 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x47, 0x69, 0x74, 0x48, + 0x00, 0x52, 0x03, 0x67, 0x69, 0x74, 0x12, 0x25, 0x0a, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, + 0x74, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x42, 0x02, 0x18, 0x01, 0x48, + 0x00, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x55, 0x72, 0x6c, 0x12, 0x61, 0x0a, + 0x18, 0x61, 0x64, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x5f, 0x65, 0x6e, 0x76, 0x5f, + 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x27, 0x2e, 0x67, 0x69, 0x74, 0x70, 0x6f, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x57, 0x6f, 0x72, 0x6b, + 0x73, 0x70, 0x61, 0x63, 0x65, 0x45, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, + 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x16, 0x61, 0x64, 0x64, 0x69, 0x74, 0x69, + 0x6f, 0x6e, 0x61, 0x6c, 0x45, 0x6e, 0x76, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x73, + 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x06, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x12, 0x27, 0x0a, 0x0f, 0x77, 0x6f, 0x72, 0x6b, + 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0e, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x43, 0x6c, 0x61, 0x73, + 0x73, 0x12, 0x32, 0x0a, 0x06, 0x65, 0x64, 0x69, 0x74, 0x6f, 0x72, 0x18, 0x08, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x69, 0x74, 0x70, 0x6f, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x64, + 0x69, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x52, 0x06, 0x65, + 0x64, 0x69, 0x74, 0x6f, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x09, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x69, 0x6e, + 0x6e, 0x65, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x70, 0x69, 0x6e, 0x6e, 0x65, + 0x64, 0x12, 0x30, 0x0a, 0x14, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x5f, 0x64, 0x65, 0x66, 0x61, 0x75, + 0x6c, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x12, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x43, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x1a, 0x64, 0x0a, 0x03, 0x47, 0x69, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6c, + 0x6f, 0x6e, 0x65, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, + 0x6c, 0x6f, 0x6e, 0x65, 0x55, 0x72, 0x6c, 0x12, 0x10, 0x0a, 0x03, 0x72, 0x65, 0x66, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x72, 0x65, 0x66, 0x12, 0x2e, 0x0a, 0x13, 0x63, 0x72, 0x65, + 0x61, 0x74, 0x65, 0x5f, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x62, 0x72, 0x61, 0x6e, 0x63, 0x68, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4c, 0x6f, + 0x63, 0x61, 0x6c, 0x42, 0x72, 0x61, 0x6e, 0x63, 0x68, 0x42, 0x08, 0x0a, 0x06, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x22, 0x55, 0x0a, 0x1f, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x41, 0x6e, 0x64, + 0x53, 0x74, 0x61, 0x72, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x32, 0x0a, 0x09, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, + 0x61, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x69, 0x74, 0x70, + 0x6f, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, + 0x09, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x22, 0x6c, 0x0a, 0x15, 0x53, 0x74, + 0x61, 0x72, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, + 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x77, 0x6f, 0x72, 0x6b, 0x73, + 0x70, 0x61, 0x63, 0x65, 0x49, 0x64, 0x12, 0x30, 0x0a, 0x14, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x5f, + 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x12, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x44, 0x65, 0x66, 0x61, 0x75, + 0x6c, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0x4c, 0x0a, 0x16, 0x53, 0x74, 0x61, 0x72, + 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x32, 0x0a, 0x09, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x69, 0x74, 0x70, 0x6f, 0x64, 0x2e, 0x76, + 0x31, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x09, 0x77, 0x6f, 0x72, + 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x22, 0xea, 0x03, 0x0a, 0x09, 0x57, 0x6f, 0x72, 0x6b, 0x73, + 0x70, 0x61, 0x63, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x02, 0x69, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x65, 0x62, 0x75, 0x69, 0x6c, 0x64, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x70, 0x72, 0x65, 0x62, 0x75, 0x69, 0x6c, 0x64, + 0x12, 0x27, 0x0a, 0x0f, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x6f, 0x72, 0x67, 0x61, 0x6e, + 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, + 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, + 0x06, 0x70, 0x69, 0x6e, 0x6e, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x70, + 0x69, 0x6e, 0x6e, 0x65, 0x64, 0x12, 0x32, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, + 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x69, 0x74, 0x70, 0x6f, 0x64, 0x2e, 0x76, + 0x31, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x71, 0x0a, 0x20, 0x61, 0x64, 0x64, + 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x5f, 0x65, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, + 0x65, 0x6e, 0x74, 0x5f, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x18, 0x07, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x67, 0x69, 0x74, 0x70, 0x6f, 0x64, 0x2e, 0x76, 0x31, 0x2e, + 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x45, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, + 0x6d, 0x65, 0x6e, 0x74, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x1e, 0x61, 0x64, + 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x45, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, + 0x65, 0x6e, 0x74, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x12, 0x16, 0x0a, 0x06, + 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, + 0x67, 0x69, 0x6f, 0x6e, 0x12, 0x27, 0x0a, 0x0f, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, + 0x65, 0x5f, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x77, + 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x12, 0x32, 0x0a, + 0x06, 0x65, 0x64, 0x69, 0x74, 0x6f, 0x72, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, + 0x67, 0x69, 0x74, 0x70, 0x6f, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x64, 0x69, 0x74, 0x6f, 0x72, + 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x52, 0x06, 0x65, 0x64, 0x69, 0x74, 0x6f, + 0x72, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x5f, 0x75, 0x72, 0x6c, + 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x55, + 0x72, 0x6c, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x72, 0x65, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x69, + 0x64, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x70, 0x72, 0x65, 0x62, 0x75, 0x69, 0x6c, + 0x64, 0x49, 0x64, 0x22, 0x89, 0x03, 0x0a, 0x0f, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, + 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x2f, 0x0a, 0x05, 0x70, 0x68, 0x61, 0x73, 0x65, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x69, 0x74, 0x70, 0x6f, 0x64, 0x2e, + 0x76, 0x31, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x50, 0x68, 0x61, 0x73, + 0x65, 0x52, 0x05, 0x70, 0x68, 0x61, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, + 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, + 0x67, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, + 0x75, 0x72, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x77, 0x6f, 0x72, 0x6b, 0x73, + 0x70, 0x61, 0x63, 0x65, 0x55, 0x72, 0x6c, 0x12, 0x3c, 0x0a, 0x0a, 0x67, 0x69, 0x74, 0x5f, 0x73, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x67, 0x69, + 0x74, 0x70, 0x6f, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, + 0x65, 0x47, 0x69, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x09, 0x67, 0x69, 0x74, 0x53, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x2e, 0x0a, 0x05, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x18, 0x05, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x67, 0x69, 0x74, 0x70, 0x6f, 0x64, 0x2e, 0x76, 0x31, + 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x50, 0x6f, 0x72, 0x74, 0x52, 0x05, + 0x70, 0x6f, 0x72, 0x74, 0x73, 0x12, 0x37, 0x0a, 0x09, 0x61, 0x64, 0x6d, 0x69, 0x73, 0x73, 0x69, + 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x19, 0x2e, 0x67, 0x69, 0x74, 0x70, 0x6f, + 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x4c, 0x65, + 0x76, 0x65, 0x6c, 0x52, 0x09, 0x61, 0x64, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1f, + 0x0a, 0x0b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0a, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x49, 0x64, 0x12, + 0x3e, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x08, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x67, 0x69, 0x74, 0x70, 0x6f, 0x64, 0x2e, 0x76, 0x31, 0x2e, + 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, + 0x47, 0x0a, 0x13, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x64, + 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x12, 0x18, + 0x0a, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x22, 0xc3, 0x02, 0x0a, 0x0d, 0x57, 0x6f, 0x72, + 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x6f, + 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x37, + 0x0a, 0x06, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1f, + 0x2e, 0x67, 0x69, 0x74, 0x70, 0x6f, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x73, + 0x70, 0x61, 0x63, 0x65, 0x50, 0x6f, 0x72, 0x74, 0x2e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, + 0x06, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x3d, 0x0a, 0x08, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x21, 0x2e, 0x67, 0x69, 0x74, 0x70, 0x6f, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, - 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, - 0x71, 0x0a, 0x20, 0x61, 0x64, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x5f, 0x65, 0x6e, - 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, - 0x6c, 0x65, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x67, 0x69, 0x74, 0x70, - 0x6f, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x45, - 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, - 0x6c, 0x65, 0x52, 0x1e, 0x61, 0x64, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x45, 0x6e, + 0x65, 0x50, 0x6f, 0x72, 0x74, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x52, 0x08, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x22, 0x47, 0x0a, 0x06, 0x50, 0x6f, 0x6c, 0x69, + 0x63, 0x79, 0x12, 0x16, 0x0a, 0x12, 0x50, 0x4f, 0x4c, 0x49, 0x43, 0x59, 0x5f, 0x55, 0x4e, 0x53, + 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x12, 0x0a, 0x0e, 0x50, 0x4f, + 0x4c, 0x49, 0x43, 0x59, 0x5f, 0x50, 0x52, 0x49, 0x56, 0x41, 0x54, 0x45, 0x10, 0x01, 0x12, 0x11, + 0x0a, 0x0d, 0x50, 0x4f, 0x4c, 0x49, 0x43, 0x59, 0x5f, 0x50, 0x55, 0x42, 0x4c, 0x49, 0x43, 0x10, + 0x02, 0x22, 0x4b, 0x0a, 0x08, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x18, 0x0a, + 0x14, 0x50, 0x52, 0x4f, 0x54, 0x4f, 0x43, 0x4f, 0x4c, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, + 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x11, 0x0a, 0x0d, 0x50, 0x52, 0x4f, 0x54, 0x4f, + 0x43, 0x4f, 0x4c, 0x5f, 0x48, 0x54, 0x54, 0x50, 0x10, 0x01, 0x12, 0x12, 0x0a, 0x0e, 0x50, 0x52, + 0x4f, 0x54, 0x4f, 0x43, 0x4f, 0x4c, 0x5f, 0x48, 0x54, 0x54, 0x50, 0x53, 0x10, 0x02, 0x22, 0x8d, + 0x03, 0x0a, 0x12, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x47, 0x69, 0x74, 0x53, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6c, 0x6f, 0x6e, 0x65, 0x5f, 0x75, + 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x6c, 0x6f, 0x6e, 0x65, 0x55, + 0x72, 0x6c, 0x12, 0x16, 0x0a, 0x06, 0x62, 0x72, 0x61, 0x6e, 0x63, 0x68, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x06, 0x62, 0x72, 0x61, 0x6e, 0x63, 0x68, 0x12, 0x23, 0x0a, 0x0d, 0x6c, 0x61, + 0x74, 0x65, 0x73, 0x74, 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0c, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x12, + 0x29, 0x0a, 0x10, 0x75, 0x6e, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x65, 0x64, 0x5f, 0x66, 0x69, + 0x6c, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0f, 0x75, 0x6e, 0x63, 0x6f, 0x6d, + 0x6d, 0x69, 0x74, 0x65, 0x64, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x12, 0x34, 0x0a, 0x16, 0x74, 0x6f, + 0x74, 0x61, 0x6c, 0x5f, 0x75, 0x6e, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x65, 0x64, 0x5f, 0x66, + 0x69, 0x6c, 0x65, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x14, 0x74, 0x6f, 0x74, 0x61, + 0x6c, 0x55, 0x6e, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x65, 0x64, 0x46, 0x69, 0x6c, 0x65, 0x73, + 0x12, 0x27, 0x0a, 0x0f, 0x75, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x65, 0x64, 0x5f, 0x66, 0x69, + 0x6c, 0x65, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0e, 0x75, 0x6e, 0x74, 0x72, 0x61, + 0x63, 0x6b, 0x65, 0x64, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x12, 0x32, 0x0a, 0x15, 0x74, 0x6f, 0x74, + 0x61, 0x6c, 0x5f, 0x75, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x65, 0x64, 0x5f, 0x66, 0x69, 0x6c, + 0x65, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, 0x52, 0x13, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x55, + 0x6e, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x65, 0x64, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x12, 0x29, 0x0a, + 0x10, 0x75, 0x6e, 0x70, 0x75, 0x73, 0x68, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, + 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0f, 0x75, 0x6e, 0x70, 0x75, 0x73, 0x68, 0x65, + 0x64, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x73, 0x12, 0x34, 0x0a, 0x16, 0x74, 0x6f, 0x74, 0x61, + 0x6c, 0x5f, 0x75, 0x6e, 0x70, 0x75, 0x73, 0x68, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x69, + 0x74, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x05, 0x52, 0x14, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x55, + 0x6e, 0x70, 0x75, 0x73, 0x68, 0x65, 0x64, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x73, 0x22, 0xef, + 0x02, 0x0a, 0x0e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x50, 0x68, 0x61, 0x73, + 0x65, 0x12, 0x33, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, + 0x1f, 0x2e, 0x67, 0x69, 0x74, 0x70, 0x6f, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x57, 0x6f, 0x72, 0x6b, + 0x73, 0x70, 0x61, 0x63, 0x65, 0x50, 0x68, 0x61, 0x73, 0x65, 0x2e, 0x50, 0x68, 0x61, 0x73, 0x65, + 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x4c, 0x0a, 0x14, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x74, + 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, + 0x52, 0x12, 0x6c, 0x61, 0x73, 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, + 0x54, 0x69, 0x6d, 0x65, 0x22, 0xd9, 0x01, 0x0a, 0x05, 0x50, 0x68, 0x61, 0x73, 0x65, 0x12, 0x15, + 0x0a, 0x11, 0x50, 0x48, 0x41, 0x53, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, + 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x13, 0x0a, 0x0f, 0x50, 0x48, 0x41, 0x53, 0x45, 0x5f, 0x50, + 0x52, 0x45, 0x50, 0x41, 0x52, 0x49, 0x4e, 0x47, 0x10, 0x01, 0x12, 0x14, 0x0a, 0x10, 0x50, 0x48, + 0x41, 0x53, 0x45, 0x5f, 0x49, 0x4d, 0x41, 0x47, 0x45, 0x42, 0x55, 0x49, 0x4c, 0x44, 0x10, 0x02, + 0x12, 0x11, 0x0a, 0x0d, 0x50, 0x48, 0x41, 0x53, 0x45, 0x5f, 0x50, 0x45, 0x4e, 0x44, 0x49, 0x4e, + 0x47, 0x10, 0x03, 0x12, 0x12, 0x0a, 0x0e, 0x50, 0x48, 0x41, 0x53, 0x45, 0x5f, 0x43, 0x52, 0x45, + 0x41, 0x54, 0x49, 0x4e, 0x47, 0x10, 0x04, 0x12, 0x16, 0x0a, 0x12, 0x50, 0x48, 0x41, 0x53, 0x45, + 0x5f, 0x49, 0x4e, 0x49, 0x54, 0x49, 0x41, 0x4c, 0x49, 0x5a, 0x49, 0x4e, 0x47, 0x10, 0x05, 0x12, + 0x11, 0x0a, 0x0d, 0x50, 0x48, 0x41, 0x53, 0x45, 0x5f, 0x52, 0x55, 0x4e, 0x4e, 0x49, 0x4e, 0x47, + 0x10, 0x06, 0x12, 0x15, 0x0a, 0x11, 0x50, 0x48, 0x41, 0x53, 0x45, 0x5f, 0x49, 0x4e, 0x54, 0x45, + 0x52, 0x52, 0x55, 0x50, 0x54, 0x45, 0x44, 0x10, 0x07, 0x12, 0x12, 0x0a, 0x0e, 0x50, 0x48, 0x41, + 0x53, 0x45, 0x5f, 0x53, 0x54, 0x4f, 0x50, 0x50, 0x49, 0x4e, 0x47, 0x10, 0x08, 0x12, 0x11, 0x0a, + 0x0d, 0x50, 0x48, 0x41, 0x53, 0x45, 0x5f, 0x53, 0x54, 0x4f, 0x50, 0x50, 0x45, 0x44, 0x10, 0x09, + 0x22, 0x3f, 0x0a, 0x0f, 0x45, 0x64, 0x69, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, + 0x6e, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, + 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, + 0x6e, 0x22, 0x48, 0x0a, 0x1c, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x45, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, - 0x65, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x18, 0x08, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x12, 0x27, 0x0a, 0x0f, 0x77, 0x6f, - 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x18, 0x09, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0e, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x43, 0x6c, - 0x61, 0x73, 0x73, 0x12, 0x32, 0x0a, 0x06, 0x65, 0x64, 0x69, 0x74, 0x6f, 0x72, 0x18, 0x0a, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x69, 0x74, 0x70, 0x6f, 0x64, 0x2e, 0x76, 0x31, 0x2e, - 0x45, 0x64, 0x69, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x52, - 0x06, 0x65, 0x64, 0x69, 0x74, 0x6f, 0x72, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x65, - 0x78, 0x74, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x63, 0x6f, - 0x6e, 0x74, 0x65, 0x78, 0x74, 0x55, 0x72, 0x6c, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x72, 0x65, 0x62, - 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x70, - 0x72, 0x65, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x49, 0x64, 0x22, 0x89, 0x03, 0x0a, 0x0f, 0x57, 0x6f, - 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x2f, 0x0a, - 0x05, 0x70, 0x68, 0x61, 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, - 0x69, 0x74, 0x70, 0x6f, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, - 0x63, 0x65, 0x50, 0x68, 0x61, 0x73, 0x65, 0x52, 0x05, 0x70, 0x68, 0x61, 0x73, 0x65, 0x12, 0x18, - 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x77, 0x6f, 0x72, 0x6b, - 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0c, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x55, 0x72, 0x6c, 0x12, 0x3c, 0x0a, - 0x0a, 0x67, 0x69, 0x74, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x1d, 0x2e, 0x67, 0x69, 0x74, 0x70, 0x6f, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x57, 0x6f, - 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x47, 0x69, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, - 0x52, 0x09, 0x67, 0x69, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x2e, 0x0a, 0x05, 0x70, - 0x6f, 0x72, 0x74, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x67, 0x69, 0x74, - 0x70, 0x6f, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, - 0x50, 0x6f, 0x72, 0x74, 0x52, 0x05, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x12, 0x37, 0x0a, 0x09, 0x61, - 0x64, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x19, - 0x2e, 0x67, 0x69, 0x74, 0x70, 0x6f, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x73, - 0x73, 0x69, 0x6f, 0x6e, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x09, 0x61, 0x64, 0x6d, 0x69, 0x73, - 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, - 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x69, 0x6e, 0x73, 0x74, 0x61, - 0x6e, 0x63, 0x65, 0x49, 0x64, 0x12, 0x3e, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, - 0x6f, 0x6e, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x67, 0x69, 0x74, 0x70, - 0x6f, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x43, - 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x64, 0x69, - 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x47, 0x0a, 0x13, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, - 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x16, 0x0a, 0x06, - 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x66, 0x61, - 0x69, 0x6c, 0x65, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x22, 0xc3, - 0x02, 0x0a, 0x0d, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x50, 0x6f, 0x72, 0x74, - 0x12, 0x12, 0x0a, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, - 0x70, 0x6f, 0x72, 0x74, 0x12, 0x37, 0x0a, 0x06, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1f, 0x2e, 0x67, 0x69, 0x74, 0x70, 0x6f, 0x64, 0x2e, 0x76, 0x31, - 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x50, 0x6f, 0x72, 0x74, 0x2e, 0x50, - 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x06, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x10, 0x0a, - 0x03, 0x75, 0x72, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, - 0x3d, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x0e, 0x32, 0x21, 0x2e, 0x67, 0x69, 0x74, 0x70, 0x6f, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x57, 0x6f, - 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x50, 0x6f, 0x72, 0x74, 0x2e, 0x50, 0x72, 0x6f, 0x74, - 0x6f, 0x63, 0x6f, 0x6c, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x22, 0x47, - 0x0a, 0x06, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x16, 0x0a, 0x12, 0x50, 0x4f, 0x4c, 0x49, - 0x43, 0x59, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, - 0x12, 0x12, 0x0a, 0x0e, 0x50, 0x4f, 0x4c, 0x49, 0x43, 0x59, 0x5f, 0x50, 0x52, 0x49, 0x56, 0x41, - 0x54, 0x45, 0x10, 0x01, 0x12, 0x11, 0x0a, 0x0d, 0x50, 0x4f, 0x4c, 0x49, 0x43, 0x59, 0x5f, 0x50, - 0x55, 0x42, 0x4c, 0x49, 0x43, 0x10, 0x02, 0x22, 0x4b, 0x0a, 0x08, 0x50, 0x72, 0x6f, 0x74, 0x6f, - 0x63, 0x6f, 0x6c, 0x12, 0x18, 0x0a, 0x14, 0x50, 0x52, 0x4f, 0x54, 0x4f, 0x43, 0x4f, 0x4c, 0x5f, - 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x11, 0x0a, - 0x0d, 0x50, 0x52, 0x4f, 0x54, 0x4f, 0x43, 0x4f, 0x4c, 0x5f, 0x48, 0x54, 0x54, 0x50, 0x10, 0x01, - 0x12, 0x12, 0x0a, 0x0e, 0x50, 0x52, 0x4f, 0x54, 0x4f, 0x43, 0x4f, 0x4c, 0x5f, 0x48, 0x54, 0x54, - 0x50, 0x53, 0x10, 0x02, 0x22, 0x8d, 0x03, 0x0a, 0x12, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, - 0x63, 0x65, 0x47, 0x69, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x63, - 0x6c, 0x6f, 0x6e, 0x65, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, - 0x63, 0x6c, 0x6f, 0x6e, 0x65, 0x55, 0x72, 0x6c, 0x12, 0x16, 0x0a, 0x06, 0x62, 0x72, 0x61, 0x6e, - 0x63, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x62, 0x72, 0x61, 0x6e, 0x63, 0x68, - 0x12, 0x23, 0x0a, 0x0d, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x69, - 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x43, - 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x12, 0x29, 0x0a, 0x10, 0x75, 0x6e, 0x63, 0x6f, 0x6d, 0x6d, 0x69, - 0x74, 0x65, 0x64, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, - 0x0f, 0x75, 0x6e, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x65, 0x64, 0x46, 0x69, 0x6c, 0x65, 0x73, - 0x12, 0x34, 0x0a, 0x16, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x75, 0x6e, 0x63, 0x6f, 0x6d, 0x6d, - 0x69, 0x74, 0x65, 0x64, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, - 0x52, 0x14, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x55, 0x6e, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x65, - 0x64, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x12, 0x27, 0x0a, 0x0f, 0x75, 0x6e, 0x74, 0x72, 0x61, 0x63, - 0x6b, 0x65, 0x64, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x09, 0x52, - 0x0e, 0x75, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x65, 0x64, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x12, - 0x32, 0x0a, 0x15, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x75, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x6b, - 0x65, 0x64, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, 0x52, 0x13, - 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x55, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x65, 0x64, 0x46, 0x69, - 0x6c, 0x65, 0x73, 0x12, 0x29, 0x0a, 0x10, 0x75, 0x6e, 0x70, 0x75, 0x73, 0x68, 0x65, 0x64, 0x5f, - 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0f, 0x75, - 0x6e, 0x70, 0x75, 0x73, 0x68, 0x65, 0x64, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x73, 0x12, 0x34, - 0x0a, 0x16, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x75, 0x6e, 0x70, 0x75, 0x73, 0x68, 0x65, 0x64, - 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x05, 0x52, 0x14, - 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x55, 0x6e, 0x70, 0x75, 0x73, 0x68, 0x65, 0x64, 0x43, 0x6f, 0x6d, - 0x6d, 0x69, 0x74, 0x73, 0x22, 0xef, 0x02, 0x0a, 0x0e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, - 0x63, 0x65, 0x50, 0x68, 0x61, 0x73, 0x65, 0x12, 0x33, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1f, 0x2e, 0x67, 0x69, 0x74, 0x70, 0x6f, 0x64, 0x2e, 0x76, - 0x31, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x50, 0x68, 0x61, 0x73, 0x65, - 0x2e, 0x50, 0x68, 0x61, 0x73, 0x65, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x4c, 0x0a, 0x14, - 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x5f, - 0x74, 0x69, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, - 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, - 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x12, 0x6c, 0x61, 0x73, 0x74, 0x54, 0x72, 0x61, 0x6e, - 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x69, 0x6d, 0x65, 0x22, 0xd9, 0x01, 0x0a, 0x05, 0x50, - 0x68, 0x61, 0x73, 0x65, 0x12, 0x15, 0x0a, 0x11, 0x50, 0x48, 0x41, 0x53, 0x45, 0x5f, 0x55, 0x4e, - 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x13, 0x0a, 0x0f, 0x50, - 0x48, 0x41, 0x53, 0x45, 0x5f, 0x50, 0x52, 0x45, 0x50, 0x41, 0x52, 0x49, 0x4e, 0x47, 0x10, 0x01, - 0x12, 0x14, 0x0a, 0x10, 0x50, 0x48, 0x41, 0x53, 0x45, 0x5f, 0x49, 0x4d, 0x41, 0x47, 0x45, 0x42, - 0x55, 0x49, 0x4c, 0x44, 0x10, 0x02, 0x12, 0x11, 0x0a, 0x0d, 0x50, 0x48, 0x41, 0x53, 0x45, 0x5f, - 0x50, 0x45, 0x4e, 0x44, 0x49, 0x4e, 0x47, 0x10, 0x03, 0x12, 0x12, 0x0a, 0x0e, 0x50, 0x48, 0x41, - 0x53, 0x45, 0x5f, 0x43, 0x52, 0x45, 0x41, 0x54, 0x49, 0x4e, 0x47, 0x10, 0x04, 0x12, 0x16, 0x0a, - 0x12, 0x50, 0x48, 0x41, 0x53, 0x45, 0x5f, 0x49, 0x4e, 0x49, 0x54, 0x49, 0x41, 0x4c, 0x49, 0x5a, - 0x49, 0x4e, 0x47, 0x10, 0x05, 0x12, 0x11, 0x0a, 0x0d, 0x50, 0x48, 0x41, 0x53, 0x45, 0x5f, 0x52, - 0x55, 0x4e, 0x4e, 0x49, 0x4e, 0x47, 0x10, 0x06, 0x12, 0x15, 0x0a, 0x11, 0x50, 0x48, 0x41, 0x53, - 0x45, 0x5f, 0x49, 0x4e, 0x54, 0x45, 0x52, 0x52, 0x55, 0x50, 0x54, 0x45, 0x44, 0x10, 0x07, 0x12, - 0x12, 0x0a, 0x0e, 0x50, 0x48, 0x41, 0x53, 0x45, 0x5f, 0x53, 0x54, 0x4f, 0x50, 0x50, 0x49, 0x4e, - 0x47, 0x10, 0x08, 0x12, 0x11, 0x0a, 0x0d, 0x50, 0x48, 0x41, 0x53, 0x45, 0x5f, 0x53, 0x54, 0x4f, - 0x50, 0x50, 0x45, 0x44, 0x10, 0x09, 0x22, 0x3f, 0x0a, 0x0f, 0x45, 0x64, 0x69, 0x74, 0x6f, 0x72, - 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, - 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x18, 0x0a, - 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, - 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x48, 0x0a, 0x1c, 0x57, 0x6f, 0x72, 0x6b, 0x73, - 0x70, 0x61, 0x63, 0x65, 0x45, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x56, - 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, - 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, - 0x65, 0x2a, 0x6f, 0x0a, 0x0e, 0x41, 0x64, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x4c, 0x65, - 0x76, 0x65, 0x6c, 0x12, 0x1f, 0x0a, 0x1b, 0x41, 0x44, 0x4d, 0x49, 0x53, 0x53, 0x49, 0x4f, 0x4e, - 0x5f, 0x4c, 0x45, 0x56, 0x45, 0x4c, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, - 0x45, 0x44, 0x10, 0x00, 0x12, 0x1e, 0x0a, 0x1a, 0x41, 0x44, 0x4d, 0x49, 0x53, 0x53, 0x49, 0x4f, - 0x4e, 0x5f, 0x4c, 0x45, 0x56, 0x45, 0x4c, 0x5f, 0x4f, 0x57, 0x4e, 0x45, 0x52, 0x5f, 0x4f, 0x4e, - 0x4c, 0x59, 0x10, 0x01, 0x12, 0x1c, 0x0a, 0x18, 0x41, 0x44, 0x4d, 0x49, 0x53, 0x53, 0x49, 0x4f, - 0x4e, 0x5f, 0x4c, 0x45, 0x56, 0x45, 0x4c, 0x5f, 0x45, 0x56, 0x45, 0x52, 0x59, 0x4f, 0x4e, 0x45, - 0x10, 0x02, 0x32, 0xab, 0x02, 0x0a, 0x10, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, - 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x51, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x57, 0x6f, - 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x1e, 0x2e, 0x67, 0x69, 0x74, 0x70, 0x6f, 0x64, - 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x67, 0x69, 0x74, 0x70, 0x6f, 0x64, - 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x6b, 0x0a, 0x14, 0x57, 0x61, - 0x74, 0x63, 0x68, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, - 0x75, 0x73, 0x12, 0x26, 0x2e, 0x67, 0x69, 0x74, 0x70, 0x6f, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x57, - 0x61, 0x74, 0x63, 0x68, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x53, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x67, 0x69, 0x74, - 0x70, 0x6f, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x57, 0x61, 0x74, 0x63, 0x68, 0x57, 0x6f, 0x72, 0x6b, - 0x73, 0x70, 0x61, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x22, 0x00, 0x30, 0x01, 0x12, 0x57, 0x0a, 0x0e, 0x4c, 0x69, 0x73, 0x74, 0x57, - 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x73, 0x12, 0x20, 0x2e, 0x67, 0x69, 0x74, 0x70, - 0x6f, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, - 0x61, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x67, 0x69, - 0x74, 0x70, 0x6f, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, - 0x73, 0x70, 0x61, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, - 0x42, 0x39, 0x5a, 0x37, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, - 0x69, 0x74, 0x70, 0x6f, 0x64, 0x2d, 0x69, 0x6f, 0x2f, 0x67, 0x69, 0x74, 0x70, 0x6f, 0x64, 0x2f, - 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x73, 0x2f, 0x70, 0x75, 0x62, 0x6c, 0x69, - 0x63, 0x2d, 0x61, 0x70, 0x69, 0x2f, 0x67, 0x6f, 0x2f, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x33, + 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x2a, 0x6f, 0x0a, 0x0e, 0x41, + 0x64, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x1f, 0x0a, + 0x1b, 0x41, 0x44, 0x4d, 0x49, 0x53, 0x53, 0x49, 0x4f, 0x4e, 0x5f, 0x4c, 0x45, 0x56, 0x45, 0x4c, + 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x1e, + 0x0a, 0x1a, 0x41, 0x44, 0x4d, 0x49, 0x53, 0x53, 0x49, 0x4f, 0x4e, 0x5f, 0x4c, 0x45, 0x56, 0x45, + 0x4c, 0x5f, 0x4f, 0x57, 0x4e, 0x45, 0x52, 0x5f, 0x4f, 0x4e, 0x4c, 0x59, 0x10, 0x01, 0x12, 0x1c, + 0x0a, 0x18, 0x41, 0x44, 0x4d, 0x49, 0x53, 0x53, 0x49, 0x4f, 0x4e, 0x5f, 0x4c, 0x45, 0x56, 0x45, + 0x4c, 0x5f, 0x45, 0x56, 0x45, 0x52, 0x59, 0x4f, 0x4e, 0x45, 0x10, 0x02, 0x32, 0xf8, 0x03, 0x0a, + 0x10, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, + 0x65, 0x12, 0x51, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, + 0x65, 0x12, 0x1e, 0x2e, 0x67, 0x69, 0x74, 0x70, 0x6f, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, + 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x1f, 0x2e, 0x67, 0x69, 0x74, 0x70, 0x6f, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, + 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x00, 0x12, 0x6b, 0x0a, 0x14, 0x57, 0x61, 0x74, 0x63, 0x68, 0x57, 0x6f, 0x72, + 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x26, 0x2e, 0x67, + 0x69, 0x74, 0x70, 0x6f, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x57, 0x61, 0x74, 0x63, 0x68, 0x57, 0x6f, + 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x67, 0x69, 0x74, 0x70, 0x6f, 0x64, 0x2e, 0x76, 0x31, + 0x2e, 0x57, 0x61, 0x74, 0x63, 0x68, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x53, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x30, + 0x01, 0x12, 0x57, 0x0a, 0x0e, 0x4c, 0x69, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, + 0x63, 0x65, 0x73, 0x12, 0x20, 0x2e, 0x67, 0x69, 0x74, 0x70, 0x6f, 0x64, 0x2e, 0x76, 0x31, 0x2e, + 0x4c, 0x69, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x73, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x67, 0x69, 0x74, 0x70, 0x6f, 0x64, 0x2e, 0x76, + 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x73, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x72, 0x0a, 0x17, 0x43, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x41, 0x6e, 0x64, 0x53, 0x74, 0x61, 0x72, 0x74, 0x57, 0x6f, 0x72, 0x6b, + 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x29, 0x2e, 0x67, 0x69, 0x74, 0x70, 0x6f, 0x64, 0x2e, 0x76, + 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x41, 0x6e, 0x64, 0x53, 0x74, 0x61, 0x72, 0x74, + 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x2a, 0x2e, 0x67, 0x69, 0x74, 0x70, 0x6f, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, + 0x61, 0x74, 0x65, 0x41, 0x6e, 0x64, 0x53, 0x74, 0x61, 0x72, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x73, + 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x57, + 0x0a, 0x0e, 0x53, 0x74, 0x61, 0x72, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, + 0x12, 0x20, 0x2e, 0x67, 0x69, 0x74, 0x70, 0x6f, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x61, + 0x72, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x67, 0x69, 0x74, 0x70, 0x6f, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x53, + 0x74, 0x61, 0x72, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x39, 0x5a, 0x37, 0x67, 0x69, 0x74, 0x68, 0x75, + 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x69, 0x74, 0x70, 0x6f, 0x64, 0x2d, 0x69, 0x6f, 0x2f, + 0x67, 0x69, 0x74, 0x70, 0x6f, 0x64, 0x2f, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, + 0x73, 0x2f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x2d, 0x61, 0x70, 0x69, 0x2f, 0x67, 0x6f, 0x2f, + 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -1555,59 +2021,73 @@ func file_gitpod_v1_workspace_proto_rawDescGZIP() []byte { } var file_gitpod_v1_workspace_proto_enumTypes = make([]protoimpl.EnumInfo, 4) -var file_gitpod_v1_workspace_proto_msgTypes = make([]protoimpl.MessageInfo, 14) +var file_gitpod_v1_workspace_proto_msgTypes = make([]protoimpl.MessageInfo, 19) var file_gitpod_v1_workspace_proto_goTypes = []interface{}{ - (AdmissionLevel)(0), // 0: gitpod.v1.AdmissionLevel - (WorkspacePort_Policy)(0), // 1: gitpod.v1.WorkspacePort.Policy - (WorkspacePort_Protocol)(0), // 2: gitpod.v1.WorkspacePort.Protocol - (WorkspacePhase_Phase)(0), // 3: gitpod.v1.WorkspacePhase.Phase - (*GetWorkspaceRequest)(nil), // 4: gitpod.v1.GetWorkspaceRequest - (*GetWorkspaceResponse)(nil), // 5: gitpod.v1.GetWorkspaceResponse - (*WatchWorkspaceStatusRequest)(nil), // 6: gitpod.v1.WatchWorkspaceStatusRequest - (*WatchWorkspaceStatusResponse)(nil), // 7: gitpod.v1.WatchWorkspaceStatusResponse - (*ListWorkspacesRequest)(nil), // 8: gitpod.v1.ListWorkspacesRequest - (*ListWorkspacesResponse)(nil), // 9: gitpod.v1.ListWorkspacesResponse - (*Workspace)(nil), // 10: gitpod.v1.Workspace - (*WorkspaceStatus)(nil), // 11: gitpod.v1.WorkspaceStatus - (*WorkspaceConditions)(nil), // 12: gitpod.v1.WorkspaceConditions - (*WorkspacePort)(nil), // 13: gitpod.v1.WorkspacePort - (*WorkspaceGitStatus)(nil), // 14: gitpod.v1.WorkspaceGitStatus - (*WorkspacePhase)(nil), // 15: gitpod.v1.WorkspacePhase - (*EditorReference)(nil), // 16: gitpod.v1.EditorReference - (*WorkspaceEnvironmentVariable)(nil), // 17: gitpod.v1.WorkspaceEnvironmentVariable - (*PaginationRequest)(nil), // 18: gitpod.v1.PaginationRequest - (*PaginationResponse)(nil), // 19: gitpod.v1.PaginationResponse - (*timestamppb.Timestamp)(nil), // 20: google.protobuf.Timestamp + (AdmissionLevel)(0), // 0: gitpod.v1.AdmissionLevel + (WorkspacePort_Policy)(0), // 1: gitpod.v1.WorkspacePort.Policy + (WorkspacePort_Protocol)(0), // 2: gitpod.v1.WorkspacePort.Protocol + (WorkspacePhase_Phase)(0), // 3: gitpod.v1.WorkspacePhase.Phase + (*GetWorkspaceRequest)(nil), // 4: gitpod.v1.GetWorkspaceRequest + (*GetWorkspaceResponse)(nil), // 5: gitpod.v1.GetWorkspaceResponse + (*WatchWorkspaceStatusRequest)(nil), // 6: gitpod.v1.WatchWorkspaceStatusRequest + (*WatchWorkspaceStatusResponse)(nil), // 7: gitpod.v1.WatchWorkspaceStatusResponse + (*ListWorkspacesRequest)(nil), // 8: gitpod.v1.ListWorkspacesRequest + (*ListWorkspacesResponse)(nil), // 9: gitpod.v1.ListWorkspacesResponse + (*CreateAndStartWorkspaceRequest)(nil), // 10: gitpod.v1.CreateAndStartWorkspaceRequest + (*CreateAndStartWorkspaceResponse)(nil), // 11: gitpod.v1.CreateAndStartWorkspaceResponse + (*StartWorkspaceRequest)(nil), // 12: gitpod.v1.StartWorkspaceRequest + (*StartWorkspaceResponse)(nil), // 13: gitpod.v1.StartWorkspaceResponse + (*Workspace)(nil), // 14: gitpod.v1.Workspace + (*WorkspaceStatus)(nil), // 15: gitpod.v1.WorkspaceStatus + (*WorkspaceConditions)(nil), // 16: gitpod.v1.WorkspaceConditions + (*WorkspacePort)(nil), // 17: gitpod.v1.WorkspacePort + (*WorkspaceGitStatus)(nil), // 18: gitpod.v1.WorkspaceGitStatus + (*WorkspacePhase)(nil), // 19: gitpod.v1.WorkspacePhase + (*EditorReference)(nil), // 20: gitpod.v1.EditorReference + (*WorkspaceEnvironmentVariable)(nil), // 21: gitpod.v1.WorkspaceEnvironmentVariable + (*CreateAndStartWorkspaceRequest_Git)(nil), // 22: gitpod.v1.CreateAndStartWorkspaceRequest.Git + (*PaginationRequest)(nil), // 23: gitpod.v1.PaginationRequest + (*PaginationResponse)(nil), // 24: gitpod.v1.PaginationResponse + (*timestamppb.Timestamp)(nil), // 25: google.protobuf.Timestamp } var file_gitpod_v1_workspace_proto_depIdxs = []int32{ - 10, // 0: gitpod.v1.GetWorkspaceResponse.workspace:type_name -> gitpod.v1.Workspace - 11, // 1: gitpod.v1.WatchWorkspaceStatusResponse.status:type_name -> gitpod.v1.WorkspaceStatus - 18, // 2: gitpod.v1.ListWorkspacesRequest.pagination:type_name -> gitpod.v1.PaginationRequest - 10, // 3: gitpod.v1.ListWorkspacesResponse.workspaces:type_name -> gitpod.v1.Workspace - 19, // 4: gitpod.v1.ListWorkspacesResponse.pagination:type_name -> gitpod.v1.PaginationResponse - 11, // 5: gitpod.v1.Workspace.status:type_name -> gitpod.v1.WorkspaceStatus - 17, // 6: gitpod.v1.Workspace.additional_environment_variables:type_name -> gitpod.v1.WorkspaceEnvironmentVariable - 16, // 7: gitpod.v1.Workspace.editor:type_name -> gitpod.v1.EditorReference - 15, // 8: gitpod.v1.WorkspaceStatus.phase:type_name -> gitpod.v1.WorkspacePhase - 14, // 9: gitpod.v1.WorkspaceStatus.git_status:type_name -> gitpod.v1.WorkspaceGitStatus - 13, // 10: gitpod.v1.WorkspaceStatus.ports:type_name -> gitpod.v1.WorkspacePort - 0, // 11: gitpod.v1.WorkspaceStatus.admission:type_name -> gitpod.v1.AdmissionLevel - 12, // 12: gitpod.v1.WorkspaceStatus.conditions:type_name -> gitpod.v1.WorkspaceConditions - 1, // 13: gitpod.v1.WorkspacePort.policy:type_name -> gitpod.v1.WorkspacePort.Policy - 2, // 14: gitpod.v1.WorkspacePort.protocol:type_name -> gitpod.v1.WorkspacePort.Protocol - 3, // 15: gitpod.v1.WorkspacePhase.name:type_name -> gitpod.v1.WorkspacePhase.Phase - 20, // 16: gitpod.v1.WorkspacePhase.last_transition_time:type_name -> google.protobuf.Timestamp - 4, // 17: gitpod.v1.WorkspaceService.GetWorkspace:input_type -> gitpod.v1.GetWorkspaceRequest - 6, // 18: gitpod.v1.WorkspaceService.WatchWorkspaceStatus:input_type -> gitpod.v1.WatchWorkspaceStatusRequest - 8, // 19: gitpod.v1.WorkspaceService.ListWorkspaces:input_type -> gitpod.v1.ListWorkspacesRequest - 5, // 20: gitpod.v1.WorkspaceService.GetWorkspace:output_type -> gitpod.v1.GetWorkspaceResponse - 7, // 21: gitpod.v1.WorkspaceService.WatchWorkspaceStatus:output_type -> gitpod.v1.WatchWorkspaceStatusResponse - 9, // 22: gitpod.v1.WorkspaceService.ListWorkspaces:output_type -> gitpod.v1.ListWorkspacesResponse - 20, // [20:23] is the sub-list for method output_type - 17, // [17:20] is the sub-list for method input_type - 17, // [17:17] is the sub-list for extension type_name - 17, // [17:17] is the sub-list for extension extendee - 0, // [0:17] is the sub-list for field type_name + 14, // 0: gitpod.v1.GetWorkspaceResponse.workspace:type_name -> gitpod.v1.Workspace + 15, // 1: gitpod.v1.WatchWorkspaceStatusResponse.status:type_name -> gitpod.v1.WorkspaceStatus + 23, // 2: gitpod.v1.ListWorkspacesRequest.pagination:type_name -> gitpod.v1.PaginationRequest + 14, // 3: gitpod.v1.ListWorkspacesResponse.workspaces:type_name -> gitpod.v1.Workspace + 24, // 4: gitpod.v1.ListWorkspacesResponse.pagination:type_name -> gitpod.v1.PaginationResponse + 22, // 5: gitpod.v1.CreateAndStartWorkspaceRequest.git:type_name -> gitpod.v1.CreateAndStartWorkspaceRequest.Git + 21, // 6: gitpod.v1.CreateAndStartWorkspaceRequest.additional_env_variables:type_name -> gitpod.v1.WorkspaceEnvironmentVariable + 20, // 7: gitpod.v1.CreateAndStartWorkspaceRequest.editor:type_name -> gitpod.v1.EditorReference + 14, // 8: gitpod.v1.CreateAndStartWorkspaceResponse.workspace:type_name -> gitpod.v1.Workspace + 14, // 9: gitpod.v1.StartWorkspaceResponse.workspace:type_name -> gitpod.v1.Workspace + 15, // 10: gitpod.v1.Workspace.status:type_name -> gitpod.v1.WorkspaceStatus + 21, // 11: gitpod.v1.Workspace.additional_environment_variables:type_name -> gitpod.v1.WorkspaceEnvironmentVariable + 20, // 12: gitpod.v1.Workspace.editor:type_name -> gitpod.v1.EditorReference + 19, // 13: gitpod.v1.WorkspaceStatus.phase:type_name -> gitpod.v1.WorkspacePhase + 18, // 14: gitpod.v1.WorkspaceStatus.git_status:type_name -> gitpod.v1.WorkspaceGitStatus + 17, // 15: gitpod.v1.WorkspaceStatus.ports:type_name -> gitpod.v1.WorkspacePort + 0, // 16: gitpod.v1.WorkspaceStatus.admission:type_name -> gitpod.v1.AdmissionLevel + 16, // 17: gitpod.v1.WorkspaceStatus.conditions:type_name -> gitpod.v1.WorkspaceConditions + 1, // 18: gitpod.v1.WorkspacePort.policy:type_name -> gitpod.v1.WorkspacePort.Policy + 2, // 19: gitpod.v1.WorkspacePort.protocol:type_name -> gitpod.v1.WorkspacePort.Protocol + 3, // 20: gitpod.v1.WorkspacePhase.name:type_name -> gitpod.v1.WorkspacePhase.Phase + 25, // 21: gitpod.v1.WorkspacePhase.last_transition_time:type_name -> google.protobuf.Timestamp + 4, // 22: gitpod.v1.WorkspaceService.GetWorkspace:input_type -> gitpod.v1.GetWorkspaceRequest + 6, // 23: gitpod.v1.WorkspaceService.WatchWorkspaceStatus:input_type -> gitpod.v1.WatchWorkspaceStatusRequest + 8, // 24: gitpod.v1.WorkspaceService.ListWorkspaces:input_type -> gitpod.v1.ListWorkspacesRequest + 10, // 25: gitpod.v1.WorkspaceService.CreateAndStartWorkspace:input_type -> gitpod.v1.CreateAndStartWorkspaceRequest + 12, // 26: gitpod.v1.WorkspaceService.StartWorkspace:input_type -> gitpod.v1.StartWorkspaceRequest + 5, // 27: gitpod.v1.WorkspaceService.GetWorkspace:output_type -> gitpod.v1.GetWorkspaceResponse + 7, // 28: gitpod.v1.WorkspaceService.WatchWorkspaceStatus:output_type -> gitpod.v1.WatchWorkspaceStatusResponse + 9, // 29: gitpod.v1.WorkspaceService.ListWorkspaces:output_type -> gitpod.v1.ListWorkspacesResponse + 11, // 30: gitpod.v1.WorkspaceService.CreateAndStartWorkspace:output_type -> gitpod.v1.CreateAndStartWorkspaceResponse + 13, // 31: gitpod.v1.WorkspaceService.StartWorkspace:output_type -> gitpod.v1.StartWorkspaceResponse + 27, // [27:32] is the sub-list for method output_type + 22, // [22:27] is the sub-list for method input_type + 22, // [22:22] is the sub-list for extension type_name + 22, // [22:22] is the sub-list for extension extendee + 0, // [0:22] is the sub-list for field type_name } func init() { file_gitpod_v1_workspace_proto_init() } @@ -1690,7 +2170,7 @@ func file_gitpod_v1_workspace_proto_init() { } } file_gitpod_v1_workspace_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Workspace); i { + switch v := v.(*CreateAndStartWorkspaceRequest); i { case 0: return &v.state case 1: @@ -1702,7 +2182,7 @@ func file_gitpod_v1_workspace_proto_init() { } } file_gitpod_v1_workspace_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*WorkspaceStatus); i { + switch v := v.(*CreateAndStartWorkspaceResponse); i { case 0: return &v.state case 1: @@ -1714,7 +2194,7 @@ func file_gitpod_v1_workspace_proto_init() { } } file_gitpod_v1_workspace_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*WorkspaceConditions); i { + switch v := v.(*StartWorkspaceRequest); i { case 0: return &v.state case 1: @@ -1726,7 +2206,7 @@ func file_gitpod_v1_workspace_proto_init() { } } file_gitpod_v1_workspace_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*WorkspacePort); i { + switch v := v.(*StartWorkspaceResponse); i { case 0: return &v.state case 1: @@ -1738,7 +2218,7 @@ func file_gitpod_v1_workspace_proto_init() { } } file_gitpod_v1_workspace_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*WorkspaceGitStatus); i { + switch v := v.(*Workspace); i { case 0: return &v.state case 1: @@ -1750,7 +2230,7 @@ func file_gitpod_v1_workspace_proto_init() { } } file_gitpod_v1_workspace_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*WorkspacePhase); i { + switch v := v.(*WorkspaceStatus); i { case 0: return &v.state case 1: @@ -1762,7 +2242,7 @@ func file_gitpod_v1_workspace_proto_init() { } } file_gitpod_v1_workspace_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*EditorReference); i { + switch v := v.(*WorkspaceConditions); i { case 0: return &v.state case 1: @@ -1774,6 +2254,54 @@ func file_gitpod_v1_workspace_proto_init() { } } file_gitpod_v1_workspace_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*WorkspacePort); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_gitpod_v1_workspace_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*WorkspaceGitStatus); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_gitpod_v1_workspace_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*WorkspacePhase); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_gitpod_v1_workspace_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*EditorReference); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_gitpod_v1_workspace_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*WorkspaceEnvironmentVariable); i { case 0: return &v.state @@ -1785,6 +2313,22 @@ func file_gitpod_v1_workspace_proto_init() { return nil } } + file_gitpod_v1_workspace_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CreateAndStartWorkspaceRequest_Git); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + file_gitpod_v1_workspace_proto_msgTypes[6].OneofWrappers = []interface{}{ + (*CreateAndStartWorkspaceRequest_Git_)(nil), + (*CreateAndStartWorkspaceRequest_ContextUrl)(nil), } type x struct{} out := protoimpl.TypeBuilder{ @@ -1792,7 +2336,7 @@ func file_gitpod_v1_workspace_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_gitpod_v1_workspace_proto_rawDesc, NumEnums: 4, - NumMessages: 14, + NumMessages: 19, NumExtensions: 0, NumServices: 1, }, diff --git a/components/public-api/go/v1/workspace_grpc.pb.go b/components/public-api/go/v1/workspace_grpc.pb.go index 2998e9106ac609..38576c6b5b3688 100644 --- a/components/public-api/go/v1/workspace_grpc.pb.go +++ b/components/public-api/go/v1/workspace_grpc.pb.go @@ -31,12 +31,17 @@ type WorkspaceServiceClient interface { // +return NOT_FOUND User does not have access to a workspace with the given // ID +return NOT_FOUND Workspace does not exist GetWorkspace(ctx context.Context, in *GetWorkspaceRequest, opts ...grpc.CallOption) (*GetWorkspaceResponse, error) - // WatchWorkspaceStatus watchs the workspaces status changes + // WatchWorkspaceStatus watches the workspaces status changes // // workspace_id +return NOT_FOUND Workspace does not exist WatchWorkspaceStatus(ctx context.Context, in *WatchWorkspaceStatusRequest, opts ...grpc.CallOption) (WorkspaceService_WatchWorkspaceStatusClient, error) // ListWorkspaces returns a list of workspaces that match the query. ListWorkspaces(ctx context.Context, in *ListWorkspacesRequest, opts ...grpc.CallOption) (*ListWorkspacesResponse, error) + // CreateAndStartWorkspace creates a new workspace and starts it. + CreateAndStartWorkspace(ctx context.Context, in *CreateAndStartWorkspaceRequest, opts ...grpc.CallOption) (*CreateAndStartWorkspaceResponse, error) + // StartWorkspace starts an existing workspace. + // If the specified workspace is not in stopped phase, this will return the workspace as is. + StartWorkspace(ctx context.Context, in *StartWorkspaceRequest, opts ...grpc.CallOption) (*StartWorkspaceResponse, error) } type workspaceServiceClient struct { @@ -97,6 +102,24 @@ func (c *workspaceServiceClient) ListWorkspaces(ctx context.Context, in *ListWor return out, nil } +func (c *workspaceServiceClient) CreateAndStartWorkspace(ctx context.Context, in *CreateAndStartWorkspaceRequest, opts ...grpc.CallOption) (*CreateAndStartWorkspaceResponse, error) { + out := new(CreateAndStartWorkspaceResponse) + err := c.cc.Invoke(ctx, "/gitpod.v1.WorkspaceService/CreateAndStartWorkspace", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *workspaceServiceClient) StartWorkspace(ctx context.Context, in *StartWorkspaceRequest, opts ...grpc.CallOption) (*StartWorkspaceResponse, error) { + out := new(StartWorkspaceResponse) + err := c.cc.Invoke(ctx, "/gitpod.v1.WorkspaceService/StartWorkspace", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // WorkspaceServiceServer is the server API for WorkspaceService service. // All implementations must embed UnimplementedWorkspaceServiceServer // for forward compatibility @@ -106,12 +129,17 @@ type WorkspaceServiceServer interface { // +return NOT_FOUND User does not have access to a workspace with the given // ID +return NOT_FOUND Workspace does not exist GetWorkspace(context.Context, *GetWorkspaceRequest) (*GetWorkspaceResponse, error) - // WatchWorkspaceStatus watchs the workspaces status changes + // WatchWorkspaceStatus watches the workspaces status changes // // workspace_id +return NOT_FOUND Workspace does not exist WatchWorkspaceStatus(*WatchWorkspaceStatusRequest, WorkspaceService_WatchWorkspaceStatusServer) error // ListWorkspaces returns a list of workspaces that match the query. ListWorkspaces(context.Context, *ListWorkspacesRequest) (*ListWorkspacesResponse, error) + // CreateAndStartWorkspace creates a new workspace and starts it. + CreateAndStartWorkspace(context.Context, *CreateAndStartWorkspaceRequest) (*CreateAndStartWorkspaceResponse, error) + // StartWorkspace starts an existing workspace. + // If the specified workspace is not in stopped phase, this will return the workspace as is. + StartWorkspace(context.Context, *StartWorkspaceRequest) (*StartWorkspaceResponse, error) mustEmbedUnimplementedWorkspaceServiceServer() } @@ -128,6 +156,12 @@ func (UnimplementedWorkspaceServiceServer) WatchWorkspaceStatus(*WatchWorkspaceS func (UnimplementedWorkspaceServiceServer) ListWorkspaces(context.Context, *ListWorkspacesRequest) (*ListWorkspacesResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method ListWorkspaces not implemented") } +func (UnimplementedWorkspaceServiceServer) CreateAndStartWorkspace(context.Context, *CreateAndStartWorkspaceRequest) (*CreateAndStartWorkspaceResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method CreateAndStartWorkspace not implemented") +} +func (UnimplementedWorkspaceServiceServer) StartWorkspace(context.Context, *StartWorkspaceRequest) (*StartWorkspaceResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method StartWorkspace not implemented") +} func (UnimplementedWorkspaceServiceServer) mustEmbedUnimplementedWorkspaceServiceServer() {} // UnsafeWorkspaceServiceServer may be embedded to opt out of forward compatibility for this service. @@ -198,6 +232,42 @@ func _WorkspaceService_ListWorkspaces_Handler(srv interface{}, ctx context.Conte return interceptor(ctx, in, info, handler) } +func _WorkspaceService_CreateAndStartWorkspace_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CreateAndStartWorkspaceRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(WorkspaceServiceServer).CreateAndStartWorkspace(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/gitpod.v1.WorkspaceService/CreateAndStartWorkspace", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(WorkspaceServiceServer).CreateAndStartWorkspace(ctx, req.(*CreateAndStartWorkspaceRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _WorkspaceService_StartWorkspace_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(StartWorkspaceRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(WorkspaceServiceServer).StartWorkspace(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/gitpod.v1.WorkspaceService/StartWorkspace", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(WorkspaceServiceServer).StartWorkspace(ctx, req.(*StartWorkspaceRequest)) + } + return interceptor(ctx, in, info, handler) +} + // WorkspaceService_ServiceDesc is the grpc.ServiceDesc for WorkspaceService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -213,6 +283,14 @@ var WorkspaceService_ServiceDesc = grpc.ServiceDesc{ MethodName: "ListWorkspaces", Handler: _WorkspaceService_ListWorkspaces_Handler, }, + { + MethodName: "CreateAndStartWorkspace", + Handler: _WorkspaceService_CreateAndStartWorkspace_Handler, + }, + { + MethodName: "StartWorkspace", + Handler: _WorkspaceService_StartWorkspace_Handler, + }, }, Streams: []grpc.StreamDesc{ { diff --git a/components/public-api/typescript/src/gitpod/v1/context_connect.ts b/components/public-api/typescript/src/gitpod/v1/context_connect.ts new file mode 100644 index 00000000000000..a3ad5e09287d11 --- /dev/null +++ b/components/public-api/typescript/src/gitpod/v1/context_connect.ts @@ -0,0 +1,33 @@ +/** + * Copyright (c) 2023 Gitpod GmbH. All rights reserved. + * Licensed under the GNU Affero General Public License (AGPL). + * See License.AGPL.txt in the project root for license information. + */ + +// @generated by protoc-gen-connect-es v1.1.2 with parameter "target=ts" +// @generated from file gitpod/v1/context.proto (package gitpod.v1, syntax proto3) +/* eslint-disable */ +// @ts-nocheck + +import { ParseContextRequest, ParseContextResponse } from "./context_pb.js"; +import { MethodKind } from "@bufbuild/protobuf"; + +/** + * @generated from service gitpod.v1.ContextService + */ +export const ContextService = { + typeName: "gitpod.v1.ContextService", + methods: { + /** + * ParseContext parses the url and returns the context + * + * @generated from rpc gitpod.v1.ContextService.ParseContext + */ + parseContext: { + name: "ParseContext", + I: ParseContextRequest, + O: ParseContextResponse, + kind: MethodKind.Unary, + }, + } +} as const; diff --git a/components/public-api/typescript/src/gitpod/v1/context_pb.ts b/components/public-api/typescript/src/gitpod/v1/context_pb.ts new file mode 100644 index 00000000000000..410ff6f2d669fe --- /dev/null +++ b/components/public-api/typescript/src/gitpod/v1/context_pb.ts @@ -0,0 +1,136 @@ +/** + * Copyright (c) 2023 Gitpod GmbH. All rights reserved. + * Licensed under the GNU Affero General Public License (AGPL). + * See License.AGPL.txt in the project root for license information. + */ + +// @generated by protoc-gen-es v1.3.3 with parameter "target=ts" +// @generated from file gitpod/v1/context.proto (package gitpod.v1, syntax proto3) +/* eslint-disable */ +// @ts-nocheck + +import type { BinaryReadOptions, FieldList, JsonReadOptions, JsonValue, PartialMessage, PlainMessage } from "@bufbuild/protobuf"; +import { Message, proto3 } from "@bufbuild/protobuf"; + +/** + * @generated from message gitpod.v1.ParseContextRequest + */ +export class ParseContextRequest extends Message { + /** + * @generated from field: string url = 1; + */ + url = ""; + + constructor(data?: PartialMessage) { + super(); + proto3.util.initPartial(data, this); + } + + static readonly runtime: typeof proto3 = proto3; + static readonly typeName = "gitpod.v1.ParseContextRequest"; + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: "url", kind: "scalar", T: 9 /* ScalarType.STRING */ }, + ]); + + static fromBinary(bytes: Uint8Array, options?: Partial): ParseContextRequest { + return new ParseContextRequest().fromBinary(bytes, options); + } + + static fromJson(jsonValue: JsonValue, options?: Partial): ParseContextRequest { + return new ParseContextRequest().fromJson(jsonValue, options); + } + + static fromJsonString(jsonString: string, options?: Partial): ParseContextRequest { + return new ParseContextRequest().fromJsonString(jsonString, options); + } + + static equals(a: ParseContextRequest | PlainMessage | undefined, b: ParseContextRequest | PlainMessage | undefined): boolean { + return proto3.util.equals(ParseContextRequest, a, b); + } +} + +/** + * @generated from message gitpod.v1.ParseContextResponse + */ +export class ParseContextResponse extends Message { + /** + * @generated from field: gitpod.v1.Context context = 1; + */ + context?: Context; + + constructor(data?: PartialMessage) { + super(); + proto3.util.initPartial(data, this); + } + + static readonly runtime: typeof proto3 = proto3; + static readonly typeName = "gitpod.v1.ParseContextResponse"; + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: "context", kind: "message", T: Context }, + ]); + + static fromBinary(bytes: Uint8Array, options?: Partial): ParseContextResponse { + return new ParseContextResponse().fromBinary(bytes, options); + } + + static fromJson(jsonValue: JsonValue, options?: Partial): ParseContextResponse { + return new ParseContextResponse().fromJson(jsonValue, options); + } + + static fromJsonString(jsonString: string, options?: Partial): ParseContextResponse { + return new ParseContextResponse().fromJsonString(jsonString, options); + } + + static equals(a: ParseContextResponse | PlainMessage | undefined, b: ParseContextResponse | PlainMessage | undefined): boolean { + return proto3.util.equals(ParseContextResponse, a, b); + } +} + +/** + * @generated from message gitpod.v1.Context + */ +export class Context extends Message { + /** + * @generated from field: string title = 1; + */ + title = ""; + + /** + * @generated from field: string normalized_url = 2; + */ + normalizedUrl = ""; + + /** + * @generated from field: string ref = 3; + */ + ref = ""; + + constructor(data?: PartialMessage) { + super(); + proto3.util.initPartial(data, this); + } + + static readonly runtime: typeof proto3 = proto3; + static readonly typeName = "gitpod.v1.Context"; + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: "title", kind: "scalar", T: 9 /* ScalarType.STRING */ }, + { no: 2, name: "normalized_url", kind: "scalar", T: 9 /* ScalarType.STRING */ }, + { no: 3, name: "ref", kind: "scalar", T: 9 /* ScalarType.STRING */ }, + ]); + + static fromBinary(bytes: Uint8Array, options?: Partial): Context { + return new Context().fromBinary(bytes, options); + } + + static fromJson(jsonValue: JsonValue, options?: Partial): Context { + return new Context().fromJson(jsonValue, options); + } + + static fromJsonString(jsonString: string, options?: Partial): Context { + return new Context().fromJsonString(jsonString, options); + } + + static equals(a: Context | PlainMessage | undefined, b: Context | PlainMessage | undefined): boolean { + return proto3.util.equals(Context, a, b); + } +} diff --git a/components/public-api/typescript/src/gitpod/v1/workspace_connect.ts b/components/public-api/typescript/src/gitpod/v1/workspace_connect.ts index 9df4b541a80e63..0ea430f9892986 100644 --- a/components/public-api/typescript/src/gitpod/v1/workspace_connect.ts +++ b/components/public-api/typescript/src/gitpod/v1/workspace_connect.ts @@ -9,7 +9,7 @@ /* eslint-disable */ // @ts-nocheck -import { GetWorkspaceRequest, GetWorkspaceResponse, ListWorkspacesRequest, ListWorkspacesResponse, WatchWorkspaceStatusRequest, WatchWorkspaceStatusResponse } from "./workspace_pb.js"; +import { CreateAndStartWorkspaceRequest, CreateAndStartWorkspaceResponse, GetWorkspaceRequest, GetWorkspaceResponse, ListWorkspacesRequest, ListWorkspacesResponse, StartWorkspaceRequest, StartWorkspaceResponse, WatchWorkspaceStatusRequest, WatchWorkspaceStatusResponse } from "./workspace_pb.js"; import { MethodKind } from "@bufbuild/protobuf"; /** @@ -33,7 +33,7 @@ export const WorkspaceService = { kind: MethodKind.Unary, }, /** - * WatchWorkspaceStatus watchs the workspaces status changes + * WatchWorkspaceStatus watches the workspaces status changes * * workspace_id +return NOT_FOUND Workspace does not exist * @@ -56,6 +56,28 @@ export const WorkspaceService = { O: ListWorkspacesResponse, kind: MethodKind.Unary, }, + /** + * CreateAndStartWorkspace creates a new workspace and starts it. + * + * @generated from rpc gitpod.v1.WorkspaceService.CreateAndStartWorkspace + */ + createAndStartWorkspace: { + name: "CreateAndStartWorkspace", + I: CreateAndStartWorkspaceRequest, + O: CreateAndStartWorkspaceResponse, + kind: MethodKind.Unary, + }, + /** + * StartWorkspace starts an existing workspace. + * If the specified workspace is not in stopped phase, this will return the workspace as is. + * + * @generated from rpc gitpod.v1.WorkspaceService.StartWorkspace + */ + startWorkspace: { + name: "StartWorkspace", + I: StartWorkspaceRequest, + O: StartWorkspaceResponse, + kind: MethodKind.Unary, + }, } } as const; - diff --git a/components/public-api/typescript/src/gitpod/v1/workspace_pb.ts b/components/public-api/typescript/src/gitpod/v1/workspace_pb.ts index 06cf7e593f2388..a7f39fcbcf1628 100644 --- a/components/public-api/typescript/src/gitpod/v1/workspace_pb.ts +++ b/components/public-api/typescript/src/gitpod/v1/workspace_pb.ts @@ -325,6 +325,315 @@ export class ListWorkspacesResponse extends Message { } } +/** + * @generated from message gitpod.v1.CreateAndStartWorkspaceRequest + */ +export class CreateAndStartWorkspaceRequest extends Message { + /** + * organization_id is the ID of the organization to create the workspace + * + * +required + * + * @generated from field: string organization_id = 1; + */ + organizationId = ""; + + /** + * configuration_id is the ID of the configuration to use + * + * @generated from field: string configuration_id = 2; + */ + configurationId = ""; + + /** + * source describes the source refer of workspace. + * + * +required + * + * @generated from oneof gitpod.v1.CreateAndStartWorkspaceRequest.source + */ + source: { + /** + * git describes the source refer of workspace + * Obtain available git using the ContextService.ParseContext operation if + * not sure about it. + * + * @generated from field: gitpod.v1.CreateAndStartWorkspaceRequest.Git git = 3; + */ + value: CreateAndStartWorkspaceRequest_Git; + case: "git"; + } | { + /** + * context_url is for backward compatiblity with the current dashboard, use + * ContextService.ParseContext get get a Git source instead + * + * @generated from field: string context_url = 4 [deprecated = true]; + * @deprecated + */ + value: string; + case: "contextUrl"; + } | { case: undefined; value?: undefined } = { case: undefined }; + + /** + * additional_env_variables provide additional environment variables to the + * workspace. + * It will take precedence over environment variables provided by + * the user and the configuration + * + * @generated from field: repeated gitpod.v1.WorkspaceEnvironmentVariable additional_env_variables = 5; + */ + additionalEnvVariables: WorkspaceEnvironmentVariable[] = []; + + /** + * @generated from field: string region = 6; + */ + region = ""; + + /** + * @generated from field: string workspace_class = 7; + */ + workspaceClass = ""; + + /** + * @generated from field: gitpod.v1.EditorReference editor = 8; + */ + editor?: EditorReference; + + /** + * @generated from field: string name = 9; + */ + name = ""; + + /** + * @generated from field: bool pinned = 10; + */ + pinned = false; + + /** + * force_default_config indicates that the workspace should be created with + * the default configuration instead of the configuration provided in + * `.gitpod.yml` file + * + * @generated from field: bool force_default_config = 11; + */ + forceDefaultConfig = false; + + constructor(data?: PartialMessage) { + super(); + proto3.util.initPartial(data, this); + } + + static readonly runtime: typeof proto3 = proto3; + static readonly typeName = "gitpod.v1.CreateAndStartWorkspaceRequest"; + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: "organization_id", kind: "scalar", T: 9 /* ScalarType.STRING */ }, + { no: 2, name: "configuration_id", kind: "scalar", T: 9 /* ScalarType.STRING */ }, + { no: 3, name: "git", kind: "message", T: CreateAndStartWorkspaceRequest_Git, oneof: "source" }, + { no: 4, name: "context_url", kind: "scalar", T: 9 /* ScalarType.STRING */, oneof: "source" }, + { no: 5, name: "additional_env_variables", kind: "message", T: WorkspaceEnvironmentVariable, repeated: true }, + { no: 6, name: "region", kind: "scalar", T: 9 /* ScalarType.STRING */ }, + { no: 7, name: "workspace_class", kind: "scalar", T: 9 /* ScalarType.STRING */ }, + { no: 8, name: "editor", kind: "message", T: EditorReference }, + { no: 9, name: "name", kind: "scalar", T: 9 /* ScalarType.STRING */ }, + { no: 10, name: "pinned", kind: "scalar", T: 8 /* ScalarType.BOOL */ }, + { no: 11, name: "force_default_config", kind: "scalar", T: 8 /* ScalarType.BOOL */ }, + ]); + + static fromBinary(bytes: Uint8Array, options?: Partial): CreateAndStartWorkspaceRequest { + return new CreateAndStartWorkspaceRequest().fromBinary(bytes, options); + } + + static fromJson(jsonValue: JsonValue, options?: Partial): CreateAndStartWorkspaceRequest { + return new CreateAndStartWorkspaceRequest().fromJson(jsonValue, options); + } + + static fromJsonString(jsonString: string, options?: Partial): CreateAndStartWorkspaceRequest { + return new CreateAndStartWorkspaceRequest().fromJsonString(jsonString, options); + } + + static equals(a: CreateAndStartWorkspaceRequest | PlainMessage | undefined, b: CreateAndStartWorkspaceRequest | PlainMessage | undefined): boolean { + return proto3.util.equals(CreateAndStartWorkspaceRequest, a, b); + } +} + +/** + * @generated from message gitpod.v1.CreateAndStartWorkspaceRequest.Git + */ +export class CreateAndStartWorkspaceRequest_Git extends Message { + /** + * clone_url is the URL of the repository to clone + * + * @generated from field: string clone_url = 1; + */ + cloneUrl = ""; + + /** + * ref is an alternatively symbolic. e.g. refs/tags/v1.0, + * empty string means the default branch of the repository + * + * @generated from field: string ref = 2; + */ + ref = ""; + + /** + * create_local_branch is the branch you want to create based on provided + * clone_url and ref when workspace started + * + * @generated from field: string create_local_branch = 3; + */ + createLocalBranch = ""; + + constructor(data?: PartialMessage) { + super(); + proto3.util.initPartial(data, this); + } + + static readonly runtime: typeof proto3 = proto3; + static readonly typeName = "gitpod.v1.CreateAndStartWorkspaceRequest.Git"; + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: "clone_url", kind: "scalar", T: 9 /* ScalarType.STRING */ }, + { no: 2, name: "ref", kind: "scalar", T: 9 /* ScalarType.STRING */ }, + { no: 3, name: "create_local_branch", kind: "scalar", T: 9 /* ScalarType.STRING */ }, + ]); + + static fromBinary(bytes: Uint8Array, options?: Partial): CreateAndStartWorkspaceRequest_Git { + return new CreateAndStartWorkspaceRequest_Git().fromBinary(bytes, options); + } + + static fromJson(jsonValue: JsonValue, options?: Partial): CreateAndStartWorkspaceRequest_Git { + return new CreateAndStartWorkspaceRequest_Git().fromJson(jsonValue, options); + } + + static fromJsonString(jsonString: string, options?: Partial): CreateAndStartWorkspaceRequest_Git { + return new CreateAndStartWorkspaceRequest_Git().fromJsonString(jsonString, options); + } + + static equals(a: CreateAndStartWorkspaceRequest_Git | PlainMessage | undefined, b: CreateAndStartWorkspaceRequest_Git | PlainMessage | undefined): boolean { + return proto3.util.equals(CreateAndStartWorkspaceRequest_Git, a, b); + } +} + +/** + * @generated from message gitpod.v1.CreateAndStartWorkspaceResponse + */ +export class CreateAndStartWorkspaceResponse extends Message { + /** + * @generated from field: gitpod.v1.Workspace workspace = 1; + */ + workspace?: Workspace; + + constructor(data?: PartialMessage) { + super(); + proto3.util.initPartial(data, this); + } + + static readonly runtime: typeof proto3 = proto3; + static readonly typeName = "gitpod.v1.CreateAndStartWorkspaceResponse"; + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: "workspace", kind: "message", T: Workspace }, + ]); + + static fromBinary(bytes: Uint8Array, options?: Partial): CreateAndStartWorkspaceResponse { + return new CreateAndStartWorkspaceResponse().fromBinary(bytes, options); + } + + static fromJson(jsonValue: JsonValue, options?: Partial): CreateAndStartWorkspaceResponse { + return new CreateAndStartWorkspaceResponse().fromJson(jsonValue, options); + } + + static fromJsonString(jsonString: string, options?: Partial): CreateAndStartWorkspaceResponse { + return new CreateAndStartWorkspaceResponse().fromJsonString(jsonString, options); + } + + static equals(a: CreateAndStartWorkspaceResponse | PlainMessage | undefined, b: CreateAndStartWorkspaceResponse | PlainMessage | undefined): boolean { + return proto3.util.equals(CreateAndStartWorkspaceResponse, a, b); + } +} + +/** + * @generated from message gitpod.v1.StartWorkspaceRequest + */ +export class StartWorkspaceRequest extends Message { + /** + * workspace_id specifies the workspace that is going to start + * + * +required + * + * @generated from field: string workspace_id = 1; + */ + workspaceId = ""; + + /** + * @generated from field: bool force_default_config = 2; + */ + forceDefaultConfig = false; + + constructor(data?: PartialMessage) { + super(); + proto3.util.initPartial(data, this); + } + + static readonly runtime: typeof proto3 = proto3; + static readonly typeName = "gitpod.v1.StartWorkspaceRequest"; + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: "workspace_id", kind: "scalar", T: 9 /* ScalarType.STRING */ }, + { no: 2, name: "force_default_config", kind: "scalar", T: 8 /* ScalarType.BOOL */ }, + ]); + + static fromBinary(bytes: Uint8Array, options?: Partial): StartWorkspaceRequest { + return new StartWorkspaceRequest().fromBinary(bytes, options); + } + + static fromJson(jsonValue: JsonValue, options?: Partial): StartWorkspaceRequest { + return new StartWorkspaceRequest().fromJson(jsonValue, options); + } + + static fromJsonString(jsonString: string, options?: Partial): StartWorkspaceRequest { + return new StartWorkspaceRequest().fromJsonString(jsonString, options); + } + + static equals(a: StartWorkspaceRequest | PlainMessage | undefined, b: StartWorkspaceRequest | PlainMessage | undefined): boolean { + return proto3.util.equals(StartWorkspaceRequest, a, b); + } +} + +/** + * @generated from message gitpod.v1.StartWorkspaceResponse + */ +export class StartWorkspaceResponse extends Message { + /** + * @generated from field: gitpod.v1.Workspace workspace = 1; + */ + workspace?: Workspace; + + constructor(data?: PartialMessage) { + super(); + proto3.util.initPartial(data, this); + } + + static readonly runtime: typeof proto3 = proto3; + static readonly typeName = "gitpod.v1.StartWorkspaceResponse"; + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: "workspace", kind: "message", T: Workspace }, + ]); + + static fromBinary(bytes: Uint8Array, options?: Partial): StartWorkspaceResponse { + return new StartWorkspaceResponse().fromBinary(bytes, options); + } + + static fromJson(jsonValue: JsonValue, options?: Partial): StartWorkspaceResponse { + return new StartWorkspaceResponse().fromJson(jsonValue, options); + } + + static fromJsonString(jsonString: string, options?: Partial): StartWorkspaceResponse { + return new StartWorkspaceResponse().fromJsonString(jsonString, options); + } + + static equals(a: StartWorkspaceResponse | PlainMessage | undefined, b: StartWorkspaceResponse | PlainMessage | undefined): boolean { + return proto3.util.equals(StartWorkspaceResponse, a, b); + } +} + /** * +resource get workspace * diff --git a/components/server/src/api/server.ts b/components/server/src/api/server.ts index a01db5034cc64c..93f335f052564c 100644 --- a/components/server/src/api/server.ts +++ b/components/server/src/api/server.ts @@ -157,6 +157,7 @@ export class API { requestMethod: `${grpc_service}/${prop as string}`, startTime: performance.now(), signal: connectContext.signal, + headers: connectContext.requestHeader, }; const withRequestContext = (fn: () => T): T => runWithRequestContext(requestContext, fn); diff --git a/components/server/src/api/teams.spec.db.ts b/components/server/src/api/teams.spec.db.ts index e9aaf13f6b25ef..f2304d5defa3ce 100644 --- a/components/server/src/api/teams.spec.db.ts +++ b/components/server/src/api/teams.spec.db.ts @@ -30,6 +30,8 @@ import { AuthProviderService } from "../auth/auth-provider-service"; import { BearerAuth } from "../auth/bearer-authenticator"; import { EnvVarService } from "../user/env-var-service"; import { ScmService } from "../scm/scm-service"; +import { ContextService } from "../workspace/context-service"; +import { ContextParser } from "../workspace/context-parser-service"; const expect = chai.expect; @@ -57,6 +59,8 @@ export class APITeamsServiceSpec { this.container.bind(AuthProviderService).toConstantValue({} as AuthProviderService); this.container.bind(EnvVarService).toConstantValue({} as EnvVarService); this.container.bind(ScmService).toConstantValue({} as ScmService); + this.container.bind(ContextService).toConstantValue({} as ContextService); + this.container.bind(ContextParser).toConstantValue({} as ContextParser); // Clean-up database const typeorm = testContainer.get(TypeORM); diff --git a/components/server/src/api/workspace-service-api.ts b/components/server/src/api/workspace-service-api.ts index 041afbf992765a..59b9d5ad27aa45 100644 --- a/components/server/src/api/workspace-service-api.ts +++ b/components/server/src/api/workspace-service-api.ts @@ -7,8 +7,12 @@ import { HandlerContext, ServiceImpl } from "@connectrpc/connect"; import { WorkspaceService as WorkspaceServiceInterface } from "@gitpod/public-api/lib/gitpod/v1/workspace_connect"; import { + CreateAndStartWorkspaceRequest, + CreateAndStartWorkspaceResponse, GetWorkspaceRequest, GetWorkspaceResponse, + StartWorkspaceRequest, + StartWorkspaceResponse, WatchWorkspaceStatusRequest, WatchWorkspaceStatusResponse, ListWorkspacesRequest, @@ -17,19 +21,22 @@ import { import { inject, injectable } from "inversify"; import { WorkspaceService } from "../workspace/workspace-service"; import { PublicAPIConverter } from "@gitpod/gitpod-protocol/lib/public-api-converter"; -import { ctxSignal, ctxUserId } from "../util/request-context"; +import { ctxClientRegion, ctxSignal, ctxUserId } from "../util/request-context"; import { parsePagination } from "@gitpod/gitpod-protocol/lib/public-api-pagination"; import { PaginationResponse } from "@gitpod/public-api/lib/gitpod/v1/pagination_pb"; import { validate as uuidValidate } from "uuid"; import { ApplicationError, ErrorCodes } from "@gitpod/gitpod-protocol/lib/messaging/error"; +import { ContextService } from "../workspace/context-service"; +import { UserService } from "../user/user-service"; +import { ContextParser } from "../workspace/context-parser-service"; @injectable() export class WorkspaceServiceAPI implements ServiceImpl { - @inject(WorkspaceService) - private readonly workspaceService: WorkspaceService; - - @inject(PublicAPIConverter) - private readonly apiConverter: PublicAPIConverter; + @inject(WorkspaceService) private readonly workspaceService: WorkspaceService; + @inject(PublicAPIConverter) private readonly apiConverter: PublicAPIConverter; + @inject(ContextService) private readonly contextService: ContextService; + @inject(UserService) private readonly userService: UserService; + @inject(ContextParser) private contextParser: ContextParser; async getWorkspace(req: GetWorkspaceRequest, _: HandlerContext): Promise { if (!req.workspaceId) { @@ -92,4 +99,79 @@ export class WorkspaceServiceAPI implements ServiceImpl { + // We rely on FGA to do the permission checking + if (req.source?.case !== "contextUrl") { + throw new ApplicationError(ErrorCodes.UNIMPLEMENTED, "not implemented"); + } + if (!req.organizationId || !uuidValidate(req.organizationId)) { + throw new ApplicationError(ErrorCodes.BAD_REQUEST, "organizationId is required"); + } + if (!req.editor) { + throw new ApplicationError(ErrorCodes.BAD_REQUEST, "editor is required"); + } + if (!req.source.value) { + throw new ApplicationError(ErrorCodes.BAD_REQUEST, "source is required"); + } + const contextUrl = req.source.value; + const user = await this.userService.findUserById(ctxUserId(), ctxUserId()); + const { context, project } = await this.contextService.parseContext(user, contextUrl, { + projectId: req.configurationId, + organizationId: req.organizationId, + forceDefaultConfig: req.forceDefaultConfig, + }); + + const normalizedContextUrl = this.contextParser.normalizeContextURL(contextUrl); + const workspace = await this.workspaceService.createWorkspace( + {}, + user, + req.organizationId, + project, + context, + normalizedContextUrl, + ); + + await this.workspaceService.startWorkspace({}, user, workspace.id, { + forceDefaultImage: req.forceDefaultConfig, + workspaceClass: req.workspaceClass, + ideSettings: { + defaultIde: req.editor.name, + useLatestVersion: req.editor.version === "latest", + }, + clientRegionCode: ctxClientRegion(), + }); + + const info = await this.workspaceService.getWorkspace(ctxUserId(), workspace.id); + const response = new CreateAndStartWorkspaceResponse(); + response.workspace = this.apiConverter.toWorkspace(info); + return response; + } + + async startWorkspace(req: StartWorkspaceRequest): Promise { + // We rely on FGA to do the permission checking + if (!req.workspaceId) { + throw new ApplicationError(ErrorCodes.BAD_REQUEST, "workspaceId is required"); + } + const user = await this.userService.findUserById(ctxUserId(), ctxUserId()); + const { workspace, latestInstance: instance } = await this.workspaceService.getWorkspace( + ctxUserId(), + req.workspaceId, + ); + if (instance && instance.status.phase !== "stopped") { + const info = await this.workspaceService.getWorkspace(ctxUserId(), workspace.id); + const response = new StartWorkspaceResponse(); + response.workspace = this.apiConverter.toWorkspace(info); + return response; + } + + await this.workspaceService.startWorkspace({}, user, workspace.id, { + forceDefaultImage: req.forceDefaultConfig, + clientRegionCode: ctxClientRegion(), + }); + const info = await this.workspaceService.getWorkspace(ctxUserId(), workspace.id); + const response = new StartWorkspaceResponse(); + response.workspace = this.apiConverter.toWorkspace(info); + return response; + } } diff --git a/components/server/src/container-module.ts b/components/server/src/container-module.ts index 31a57bac4e4e5a..fc58eaedb03035 100644 --- a/components/server/src/container-module.ts +++ b/components/server/src/container-module.ts @@ -128,6 +128,7 @@ import { WorkspaceStarter } from "./workspace/workspace-starter"; import { DefaultWorkspaceImageValidator } from "./orgs/default-workspace-image-validator"; import { ContextAwareAnalyticsWriter } from "./analytics"; import { ScmService } from "./scm/scm-service"; +import { ContextService } from "./workspace/context-service"; export const productionContainerModule = new ContainerModule( (bind, unbind, isBound, rebind, unbindAsync, onActivation, onDeactivation) => { @@ -171,6 +172,8 @@ export const productionContainerModule = new ContainerModule( bind(ServerFactory).toAutoFactory(GitpodServerImpl); bind(UserController).toSelf().inSingletonScope(); + bind(ContextService).toSelf().inSingletonScope(); + bind(GitpodServerImpl).toSelf(); bind(WebsocketConnectionManager) .toDynamicValue((ctx) => { diff --git a/components/server/src/express-util.ts b/components/server/src/express-util.ts index 83f64f010a4a60..13bd2539bd45aa 100644 --- a/components/server/src/express-util.ts +++ b/components/server/src/express-util.ts @@ -7,6 +7,7 @@ import { URL } from "url"; import express from "express"; import * as crypto from "crypto"; +import { IncomingHttpHeaders } from "http"; export const query = (...tuples: [string, string][]) => { if (tuples.length === 0) { @@ -134,3 +135,11 @@ export function clientIp(req: express.Request): string | undefined { } return clientIp.split(",")[0]; } + +export function toHeaders(headers: IncomingHttpHeaders): Headers { + const result = new Headers(); + for (const [key, value] of Object.entries(headers)) { + result.set(key, value as string); + } + return result; +} diff --git a/components/server/src/prebuilds/github-app.ts b/components/server/src/prebuilds/github-app.ts index 8eb0720290d615..3f99559c7870d1 100644 --- a/components/server/src/prebuilds/github-app.ts +++ b/components/server/src/prebuilds/github-app.ts @@ -34,7 +34,7 @@ import { TraceContext } from "@gitpod/gitpod-protocol/lib/util/tracing"; import { PrebuildManager } from "./prebuild-manager"; import { PrebuildStatusMaintainer } from "./prebuilt-status-maintainer"; import { Options, ApplicationFunctionOptions } from "probot/lib/types"; -import { asyncHandler } from "../express-util"; +import { asyncHandler, toHeaders } from "../express-util"; import { ContextParser } from "../workspace/context-parser-service"; import { HostContextProvider } from "../auth/host-context-provider"; import { RepoURL } from "../repohost"; @@ -122,6 +122,7 @@ export class GithubApp { requestKind: "probot", requestMethod: req.path, signal: new AbortController().signal, + headers: toHeaders(req.headers), }, () => next(), ); diff --git a/components/server/src/server.ts b/components/server/src/server.ts index 9418e977646448..1a4efce010f8b6 100644 --- a/components/server/src/server.ts +++ b/components/server/src/server.ts @@ -17,7 +17,7 @@ import { UserController } from "./user/user-controller"; import { EventEmitter } from "events"; import { createWebSocketConnection, toIWebSocket } from "@gitpod/gitpod-protocol/lib/messaging/node/connection"; import { WsExpressHandler, WsRequestHandler } from "./express/ws-handler"; -import { isAllowedWebsocketDomain, bottomErrorHandler, unhandledToError } from "./express-util"; +import { isAllowedWebsocketDomain, bottomErrorHandler, unhandledToError, toHeaders } from "./express-util"; import { log } from "@gitpod/gitpod-protocol/lib/util/logging"; import { AddressInfo } from "net"; import { WorkspaceDownloadService } from "./workspace/workspace-download-service"; @@ -150,6 +150,7 @@ export class Server { requestMethod: req.path, signal: new AbortController().signal, subjectId: userId ? SubjectId.fromUserId(userId) : undefined, // TODO(gpl) Can we assume this? E.g., has this been verified? It should: It means we could decode the cookie, right? + headers: toHeaders(req.headers), }, () => next(), ); diff --git a/components/server/src/util/request-context.ts b/components/server/src/util/request-context.ts index 7015696856033b..cda68a44e0cc2e 100644 --- a/components/server/src/util/request-context.ts +++ b/components/server/src/util/request-context.ts @@ -9,9 +9,10 @@ import { performance } from "node:perf_hooks"; import { v4 } from "uuid"; import { SubjectId } from "../auth/subject-id"; import { ApplicationError, ErrorCodes } from "@gitpod/gitpod-protocol/lib/messaging/error"; +import { takeFirst } from "../express-util"; /** - * ReqeuestContext is the context that all our request-handling code runs in. + * RequestContext is the context that all our request-handling code runs in. * All code has access to the contained fields by using the exported "ctx...()" functions below. * * It's meant to be the host all concerns we have for a request. For now, those are: @@ -62,6 +63,11 @@ export interface RequestContext { * The SubjectId this request is authenticated with. */ readonly subjectId?: SubjectId; + + /** + * Headers of this request + */ + readonly headers?: Headers; } const asyncLocalStorage = new AsyncLocalStorage(); @@ -98,6 +104,17 @@ export function ctxUserId(): string { return userId; } +/** + * @returns The region code with current request (provided by GLB). + */ +export function ctxClientRegion(): string | undefined { + const headers = ctxGet().headers; + if (!headers) { + return; + } + return takeFirst(headers.get("x-glb-client-region") || undefined); +} + /** * @throws 408/CANCELLED if the request has been aborted */ diff --git a/components/server/src/websocket/websocket-connection-manager.ts b/components/server/src/websocket/websocket-connection-manager.ts index 23e632d1cd6271..9e5d9dbc16a10b 100644 --- a/components/server/src/websocket/websocket-connection-manager.ts +++ b/components/server/src/websocket/websocket-connection-manager.ts @@ -36,7 +36,7 @@ import { RepositoryResourceGuard, FGAResourceAccessGuard, } from "../auth/resource-access"; -import { clientIp, takeFirst } from "../express-util"; +import { clientIp, takeFirst, toHeaders } from "../express-util"; import { increaseApiCallCounter, increaseApiConnectionClosedCounter, @@ -104,6 +104,7 @@ export interface ClientMetadata { origin: ClientOrigin; version?: string; userAgent?: string; + headers?: Headers; } interface ClientOrigin { workspaceId?: string; @@ -120,7 +121,7 @@ export namespace ClientMetadata { id = userId; authLevel = "user"; } - return { id, authLevel, userId, ...data, origin: data?.origin || {} }; + return { id, authLevel, userId, ...data, origin: data?.origin || {}, headers: data?.headers }; } export function fromRequest(req: any) { @@ -135,7 +136,13 @@ export namespace ClientMetadata { instanceId, workspaceId, }; - return ClientMetadata.from(user?.id, { type, origin, version, userAgent }); + return ClientMetadata.from(user?.id, { + type, + origin, + version, + userAgent, + headers: toHeaders(expressReq.headers), + }); } function getOriginWorkspaceId(req: express.Request): string | undefined { @@ -389,6 +396,7 @@ class GitpodJsonRpcProxyFactory extends JsonRpcProxyFactory signal: abortController.signal, subjectId: userId ? SubjectId.fromUserId(userId) : undefined, traceId: span.context().toTraceId(), + headers: this.clientMetadata.headers, }, async () => { try { diff --git a/components/server/src/workspace/context-service.ts b/components/server/src/workspace/context-service.ts new file mode 100644 index 00000000000000..0c055b0f25f946 --- /dev/null +++ b/components/server/src/workspace/context-service.ts @@ -0,0 +1,142 @@ +/** + * Copyright (c) 2023 Gitpod GmbH. All rights reserved. + * Licensed under the GNU Affero General Public License (AGPL). + * See License.AGPL.txt in the project root for license information. + */ + +import { WorkspaceDB, DBWithTracing, TracedWorkspaceDB } from "@gitpod/gitpod-db/lib"; +import { + CommitContext, + PrebuiltWorkspace, + PrebuiltWorkspaceContext, + User, + WorkspaceContext, + Project, + SnapshotContext, +} from "@gitpod/gitpod-protocol"; +import { ApplicationError, ErrorCodes } from "@gitpod/gitpod-protocol/lib/messaging/error"; +import { inject, injectable } from "inversify"; +import { ContextParser } from "./context-parser-service"; +import { ConfigProvider } from "./config-provider"; +import { ProjectsService } from "../projects/projects-service"; +import { OpenPrebuildContext, WithDefaultConfig } from "@gitpod/gitpod-protocol/lib/protocol"; +import { IncrementalWorkspaceService } from "../prebuilds/incremental-workspace-service"; +import { Authorizer } from "../authorization/authorizer"; + +@injectable() +export class ContextService { + constructor( + @inject(TracedWorkspaceDB) private readonly workspaceDb: DBWithTracing, + @inject(ContextParser) private contextParser: ContextParser, + @inject(IncrementalWorkspaceService) private readonly incrementalPrebuildsService: IncrementalWorkspaceService, + @inject(ConfigProvider) private readonly configProvider: ConfigProvider, + + @inject(ProjectsService) private readonly projectsService: ProjectsService, + + @inject(Authorizer) private readonly auth: Authorizer, + ) {} + + private async findPrebuiltWorkspace( + user: User, + projectId: string, + context: WorkspaceContext, + organizationId?: string, + ): Promise { + if (!(CommitContext.is(context) && context.repository.cloneUrl && context.revision)) { + return; + } + + const cloneUrl = context.repository.cloneUrl; + let prebuiltWorkspace: PrebuiltWorkspace | undefined; + if (OpenPrebuildContext.is(context)) { + prebuiltWorkspace = await this.workspaceDb.trace({}).findPrebuildByID(context.openPrebuildID); + if (prebuiltWorkspace?.cloneURL !== cloneUrl) { + // prevent users from opening arbitrary prebuilds this way - they must match the clone URL so that the resource guards are correct. + return undefined; + } + } else { + const configPromise = this.configProvider.fetchConfig({}, user, context, organizationId); + const history = await this.incrementalPrebuildsService.getCommitHistoryForContext(context, user); + const { config } = await configPromise; + prebuiltWorkspace = await this.incrementalPrebuildsService.findGoodBaseForIncrementalBuild( + context, + config, + history, + user, + projectId, + ); + } + if (!prebuiltWorkspace?.projectId) { + return undefined; + } + + // check if the user has access to the project + if (!(await this.auth.hasPermissionOnProject(user.id, "read_prebuild", prebuiltWorkspace.projectId))) { + return undefined; + } + const result: PrebuiltWorkspaceContext = { + title: context.title, + originalContext: context, + prebuiltWorkspace, + }; + return result; + } + + public async parseContext( + user: User, + contextUrl: string, + options?: { projectId?: string; organizationId?: string; forceDefaultConfig?: boolean }, + ): Promise<{ context: WorkspaceContext; project?: Project }> { + const normalizedContextUrl = this.contextParser.normalizeContextURL(contextUrl); + + let context = await this.contextParser.handle({}, user, normalizedContextUrl); + + if (SnapshotContext.is(context)) { + // TODO(janx): Remove snapshot access tracking once we're certain that enforcing repository read access doesn't disrupt the snapshot UX. + const snapshot = await this.workspaceDb.trace({}).findSnapshotById(context.snapshotId); + if (!snapshot) { + throw new ApplicationError( + ErrorCodes.NOT_FOUND, + "No snapshot with id '" + context.snapshotId + "' found.", + ); + } + const workspace = await this.workspaceDb.trace({}).findById(snapshot.originalWorkspaceId); + if (!workspace) { + throw new ApplicationError( + ErrorCodes.NOT_FOUND, + "No workspace with id '" + snapshot.originalWorkspaceId + "' found.", + ); + } + + // TODO: Snapshot permission check should be addressed with FGA in the future. + } + + // if we're forced to use the default config, mark the context as such + if (!!options?.forceDefaultConfig) { + context = WithDefaultConfig.mark(context); + } + + let project: Project | undefined = undefined; + if (options?.projectId) { + project = await this.projectsService.getProject(user.id, options.projectId); + } else if (CommitContext.is(context)) { + const projects = await this.projectsService.findProjectsByCloneUrl(user.id, context.repository.cloneUrl); + if (projects.length > 1) { + throw new ApplicationError(ErrorCodes.BAD_REQUEST, "Multiple projects found for clone URL."); + } + if (projects.length === 1) { + project = projects[0]; + } + } + + const prebuiltWorkspace = + project?.settings?.prebuilds?.enable && options?.organizationId + ? await this.findPrebuiltWorkspace(user, project.id, context, options.organizationId) + : undefined; + if (WorkspaceContext.is(prebuiltWorkspace)) { + context = prebuiltWorkspace; + } + + return { context, project }; + } +} diff --git a/components/server/src/workspace/gitpod-server-impl.ts b/components/server/src/workspace/gitpod-server-impl.ts index 72eaa6640e7eb3..a7ecc7c7662b95 100644 --- a/components/server/src/workspace/gitpod-server-impl.ts +++ b/components/server/src/workspace/gitpod-server-impl.ts @@ -17,7 +17,6 @@ import { BlockedRepositoryDB } from "@gitpod/gitpod-db/lib/blocked-repository-db import { AuthProviderEntry, AuthProviderInfo, - CommitContext, Configuration, DisposableCollection, GetWorkspaceTimeoutResult, @@ -27,7 +26,6 @@ import { GitpodTokenType, PermissionName, PrebuiltWorkspace, - PrebuiltWorkspaceContext, SetWorkspaceTimeoutResult, StartWorkspaceResult, Token, @@ -51,13 +49,11 @@ import { Project, ProviderRepository, TeamMemberRole, - WithDefaultConfig, FindPrebuildsParams, PrebuildWithStatus, StartPrebuildResult, ClientHeaderFields, Permission, - SnapshotContext, SSHPublicKeyValue, UserSSHPublicKeyValue, RoleOrPermission, @@ -111,6 +107,7 @@ import { ContextParser } from "./context-parser-service"; import { isClusterMaintenanceError } from "./workspace-starter"; import { HeadlessLogUrls } from "@gitpod/gitpod-protocol/lib/headless-workspace-log"; import { ConfigProvider } from "./config-provider"; +import { InvalidGitpodYMLError } from "./config-provider"; import { ProjectsService } from "../projects/projects-service"; import { IDEOptions } from "@gitpod/gitpod-protocol/lib/ide-protocol"; import { @@ -123,7 +120,6 @@ import { EmailDomainFilterEntry, EnvVarWithValue, LinkedInProfile, - OpenPrebuildContext, ProjectEnvVar, UserEnvVar, UserFeatureSettings, @@ -140,7 +136,6 @@ import { createCookielessId, maskIp } from "../analytics"; import { getExperimentsClientForBackend } from "@gitpod/gitpod-protocol/lib/experiments/configcat-server"; import { LinkedInService } from "../linkedin-service"; import { SnapshotService, WaitForSnapshotOptions } from "./snapshot-service"; -import { IncrementalWorkspaceService } from "../prebuilds/incremental-workspace-service"; import { PrebuildManager } from "../prebuilds/prebuild-manager"; import { StripeService } from "../billing/stripe-service"; import { @@ -162,6 +157,7 @@ import { EnvVarService } from "../user/env-var-service"; import { SubjectId } from "../auth/subject-id"; import { runWithSubjectId } from "../util/request-context"; import { ScmService } from "../scm/scm-service"; +import { ContextService } from "./context-service"; // shortcut export const traceWI = (ctx: TraceContext, wi: Omit) => TraceContext.setOWI(ctx, wi); // userId is already taken care of in WebsocketConnectionManager @@ -183,8 +179,6 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable { @inject(ContextParser) private contextParser: ContextParser, @inject(PrebuildManager) private readonly prebuildManager: PrebuildManager, - @inject(IncrementalWorkspaceService) private readonly incrementalPrebuildsService: IncrementalWorkspaceService, - @inject(ConfigProvider) private readonly configProvider: ConfigProvider, @inject(WorkspaceService) private readonly workspaceService: WorkspaceService, @inject(SnapshotService) private readonly snapshotService: SnapshotService, @inject(WorkspaceManagerClientProvider) @@ -224,6 +218,8 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable { @inject(EmailDomainFilterDB) private emailDomainFilterdb: EmailDomainFilterDB, @inject(RedisSubscriber) private readonly subscriber: RedisSubscriber, + + @inject(ContextService) private readonly contextService: ContextService, ) {} /** Id the uniquely identifies this server instance */ @@ -307,71 +303,6 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable { return allProjects; } - private async findPrebuiltWorkspace( - parentCtx: TraceContext, - user: User, - projectId: string, - context: WorkspaceContext, - organizationId?: string, - ): Promise { - const ctx = TraceContext.childContext("findPrebuiltWorkspace", parentCtx); - try { - if (!(CommitContext.is(context) && context.repository.cloneUrl && context.revision)) { - return; - } - - const commitSHAs = CommitContext.computeHash(context); - - const logCtx: LogContext = { userId: user.id }; - const cloneUrl = context.repository.cloneUrl; - let prebuiltWorkspace: PrebuiltWorkspace | undefined; - const logPayload = { - cloneUrl, - commit: commitSHAs, - prebuiltWorkspace, - }; - if (OpenPrebuildContext.is(context)) { - prebuiltWorkspace = await this.workspaceDb.trace(ctx).findPrebuildByID(context.openPrebuildID); - if (prebuiltWorkspace?.cloneURL !== cloneUrl) { - // prevent users from opening arbitrary prebuilds this way - they must match the clone URL so that the resource guards are correct. - return undefined; - } - } else { - log.debug(logCtx, "Looking for prebuilt workspace: ", logPayload); - const configPromise = this.configProvider.fetchConfig({}, user, context, organizationId); - const history = await this.incrementalPrebuildsService.getCommitHistoryForContext(context, user); - const { config } = await configPromise; - prebuiltWorkspace = await this.incrementalPrebuildsService.findGoodBaseForIncrementalBuild( - context, - config, - history, - user, - projectId, - ); - } - if (!prebuiltWorkspace?.projectId) { - return undefined; - } - - // check if the user has access to the project - if (!(await this.auth.hasPermissionOnProject(user.id, "read_prebuild", prebuiltWorkspace.projectId))) { - return undefined; - } - log.info(logCtx, `Found prebuilt workspace for ${cloneUrl}:${commitSHAs}`, logPayload); - const result: PrebuiltWorkspaceContext = { - title: context.title, - originalContext: context, - prebuiltWorkspace, - }; - return result; - } catch (e) { - TraceContext.setError(ctx, e); - throw e; - } finally { - ctx.span.finish(); - } - } - private listenForWorkspaceInstanceUpdates(): void { if (!this.userID || !this.client) { return; @@ -963,57 +894,6 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable { .getWorkspaceUsers(workspaceId, this.config.workspaceHeartbeat.timeoutSeconds * 1000); } - private async findRunningInstancesForContext( - ctx: TraceContext, - contextPromise: Promise, - contextUrl: string, - runningInstancesPromise: Promise, - ): Promise { - const span = TraceContext.startSpan("findRunningInstancesForContext", ctx); - try { - const runningInstances = (await runningInstancesPromise).filter( - (instance) => instance.status.phase !== "stopping", - ); - const runningInfos = await Promise.all( - runningInstances.map(async (workspaceInstance) => { - const workspace = await this.workspaceDb.trace(ctx).findById(workspaceInstance.workspaceId); - if (!workspace) { - return; - } - - const result: WorkspaceInfo = { - workspace, - latestInstance: workspaceInstance, - }; - return result; - }), - ); - - let context: WorkspaceContext; - try { - context = await contextPromise; - } catch { - return []; - } - const sameContext = (ws: WorkspaceInfo) => { - return ( - ws.workspace.contextURL === contextUrl && - CommitContext.is(ws.workspace.context) && - CommitContext.is(context) && - ws.workspace.context.revision === context.revision - ); - }; - return runningInfos - .filter((info) => info && info.workspace.type === "regular" && sameContext(info)) - .map((info) => info!); - } catch (e) { - TraceContext.setError(ctx, e); - throw e; - } finally { - span.finish(); - } - } - public async isPrebuildDone(ctx: TraceContext, pwsId: string): Promise { traceAPIParams(ctx, { pwsId }); @@ -1045,111 +925,12 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable { logContext = { userId: user.id }; - //TODO(se) remove this implicit check and let instead clients do the checking. - // Credit check runs in parallel with the other operations up until we start consuming resources. - // Make sure to await for the creditCheck promise in the right places. - const runningInstancesPromise = this.workspaceDb.trace(ctx).findRegularRunningInstances(user.id); normalizedContextUrl = this.contextParser.normalizeContextURL(contextUrl); - let runningForContextPromise: Promise = Promise.resolve([]); - const contextPromise = this.contextParser.handle(ctx, user, normalizedContextUrl); - if (!options.ignoreRunningWorkspaceOnSameCommit) { - runningForContextPromise = this.findRunningInstancesForContext( - ctx, - contextPromise, - normalizedContextUrl, - runningInstancesPromise, - ); - } - - // make sure we've checked that the user has enough credit before consuming any resources. - // Be sure to check this before prebuilds and create workspace, too! - let context = await contextPromise; - - if (SnapshotContext.is(context)) { - // TODO(janx): Remove snapshot access tracking once we're certain that enforcing repository read access doesn't disrupt the snapshot UX. - this.trackEvent(ctx, { - event: "snapshot_access_request", - properties: { snapshot_id: context.snapshotId }, - }).catch((err) => log.error("cannot track event", err)); - const snapshot = await this.workspaceDb.trace(ctx).findSnapshotById(context.snapshotId); - if (!snapshot) { - throw new ApplicationError( - ErrorCodes.NOT_FOUND, - "No snapshot with id '" + context.snapshotId + "' found.", - ); - } - const workspace = await this.workspaceDb.trace(ctx).findById(snapshot.originalWorkspaceId); - if (!workspace) { - throw new ApplicationError( - ErrorCodes.NOT_FOUND, - "No workspace with id '" + snapshot.originalWorkspaceId + "' found.", - ); - } - try { - await this.guardAccess({ kind: "snapshot", subject: snapshot, workspace }, "get"); - } catch (error) { - this.trackEvent(ctx, { - event: "snapshot_access_denied", - properties: { snapshot_id: context.snapshotId, error: String(error) }, - }).catch((err) => log.error("cannot track event", err)); - if (error instanceof UnauthorizedRepositoryAccessError) { - throw error; - } - throw new ApplicationError( - ErrorCodes.PERMISSION_DENIED, - `Snapshot URLs require read access to the underlying repository. Please request access from the repository owner.`, - ); - } - this.trackEvent(ctx, { - event: "snapshot_access_granted", - properties: { snapshot_id: context.snapshotId }, - }).catch((err) => log.error("cannot track event", err)); - } - - // if we're forced to use the default config, mark the context as such - if (!!options.forceDefaultConfig) { - context = WithDefaultConfig.mark(context); - } - - if (!options.ignoreRunningWorkspaceOnSameCommit && !context.forceCreateNewWorkspace) { - const runningForContext = await runningForContextPromise; - if (runningForContext.length > 0) { - return { existingWorkspaces: runningForContext }; - } - } - - let project: Project | undefined = undefined; - if (options.projectId) { - project = await this.projectsService.getProject(user.id, options.projectId); - } else if (CommitContext.is(context)) { - const projects = await this.projectsService.findProjectsByCloneUrl( - user.id, - context.repository.cloneUrl, - ); - if (projects.length > 1) { - throw new ApplicationError(ErrorCodes.BAD_REQUEST, "Multiple projects found for clone URL."); - } - if (projects.length === 1) { - project = projects[0]; - } - } - const mayStartWorkspacePromise = this.workspaceService.mayStartWorkspace( - ctx, - user, - options.organizationId, - runningInstancesPromise, - ); - - const prebuiltWorkspace = project?.settings?.prebuilds?.enable - ? await this.findPrebuiltWorkspace(ctx, user, project.id, context, options.organizationId) - : undefined; - if (WorkspaceContext.is(prebuiltWorkspace)) { - ctx.span?.log({ prebuild: "available" }); - context = prebuiltWorkspace; - } - - await mayStartWorkspacePromise; + const { context, project } = await this.contextService.parseContext(user, contextUrl, { + projectId: options.projectId, + organizationId: options.organizationId, + }); const workspace = await this.workspaceService.createWorkspace( ctx, diff --git a/components/server/src/workspace/workspace-service.ts b/components/server/src/workspace/workspace-service.ts index daddc5e73fe85e..971307527c4122 100644 --- a/components/server/src/workspace/workspace-service.ts +++ b/components/server/src/workspace/workspace-service.ts @@ -104,6 +104,8 @@ export class WorkspaceService { context: WorkspaceContext, normalizedContextURL: string, ): Promise { + await this.mayStartWorkspace(ctx, user, organizationId, this.db.findRegularRunningInstances(user.id)); + await this.auth.checkPermissionOnOrganization(user.id, "create_workspace", organizationId); // We don't want to be doing this in a transaction, because it calls out to external systems. @@ -502,14 +504,13 @@ export class WorkspaceService { } /** - * @deprecated TODO (gpl) This should be private, but in favor of smaller PRs, will be public for now. * @param ctx * @param user * @param organizationId * @param runningInstances * @returns */ - public async mayStartWorkspace( + private async mayStartWorkspace( ctx: TraceContext, user: User, organizationId: string, From 141e287e753bd2468fd7e01697ca332820c89c6b Mon Sep 17 00:00:00 2001 From: Huiwen Date: Wed, 22 Nov 2023 08:33:06 +0000 Subject: [PATCH 2/3] Add unit tests --- .../public-api/gitpod/v1/configuration.proto | 1 - .../public-api/gitpod/v1/workspace.proto | 15 +- .../test/service-testing-container-module.ts | 6 + .../src/workspace/context-service.spec.db.ts | 325 ++++++++++++++++++ 4 files changed, 339 insertions(+), 8 deletions(-) create mode 100644 components/server/src/workspace/context-service.spec.db.ts diff --git a/components/public-api/gitpod/v1/configuration.proto b/components/public-api/gitpod/v1/configuration.proto index cfca9460a15619..989ed728a30b7f 100644 --- a/components/public-api/gitpod/v1/configuration.proto +++ b/components/public-api/gitpod/v1/configuration.proto @@ -50,7 +50,6 @@ service ConfigurationService { // Updates a configuration. rpc UpdateConfiguration(UpdateConfigurationRequest) returns (UpdateConfigurationResponse) {} - // Deletes a configuration. rpc DeleteConfiguration(DeleteConfigurationRequest) returns (DeleteConfigurationResponse) {} } diff --git a/components/public-api/gitpod/v1/workspace.proto b/components/public-api/gitpod/v1/workspace.proto index e11bd0d7d292c9..f17cd6e08cb0b1 100644 --- a/components/public-api/gitpod/v1/workspace.proto +++ b/components/public-api/gitpod/v1/workspace.proto @@ -23,8 +23,7 @@ service WorkspaceService { rpc ListWorkspaces(ListWorkspacesRequest) returns (ListWorkspacesResponse) {} // CreateAndStartWorkspace creates a new workspace and starts it. - rpc CreateAndStartWorkspace(CreateAndStartWorkspaceRequest) - returns (CreateAndStartWorkspaceResponse) {} + rpc CreateAndStartWorkspace(CreateAndStartWorkspaceRequest) returns (CreateAndStartWorkspaceResponse) {} // StartWorkspace starts an existing workspace. // If the specified workspace is not in stopped phase, this will return the workspace as is. @@ -82,7 +81,6 @@ message ListWorkspacesResponse { } message CreateAndStartWorkspaceRequest { - message Git { // clone_url is the URL of the repository to clone string clone_url = 1; @@ -108,7 +106,6 @@ message CreateAndStartWorkspaceRequest { // // +required oneof source { - // git describes the source refer of workspace // Obtain available git using the ContextService.ParseContext operation if // not sure about it. @@ -116,7 +113,7 @@ message CreateAndStartWorkspaceRequest { // context_url is for backward compatiblity with the current dashboard, use // ContextService.ParseContext get get a Git source instead - string context_url = 4 [ deprecated = true ]; + string context_url = 4 [deprecated = true]; } // additional_env_variables provide additional environment variables to the @@ -143,7 +140,9 @@ message CreateAndStartWorkspaceRequest { bool force_default_config = 11; } -message CreateAndStartWorkspaceResponse { Workspace workspace = 1; } +message CreateAndStartWorkspaceResponse { + Workspace workspace = 1; +} message StartWorkspaceRequest { // workspace_id specifies the workspace that is going to start @@ -154,7 +153,9 @@ message StartWorkspaceRequest { bool force_default_config = 2; } -message StartWorkspaceResponse { Workspace workspace = 1; } +message StartWorkspaceResponse { + Workspace workspace = 1; +} // +resource get workspace message Workspace { diff --git a/components/server/src/test/service-testing-container-module.ts b/components/server/src/test/service-testing-container-module.ts index f337316ec6e541..104eede97e118a 100644 --- a/components/server/src/test/service-testing-container-module.ts +++ b/components/server/src/test/service-testing-container-module.ts @@ -293,6 +293,12 @@ const mockApplyingContainerModule = new ContainerModule((bind, unbound, isbound, authProviderConfigs: [], installationShortname: "gitpod", auth: mockAuthConfig, + prebuildLimiter: { + "*": { + limit: 50, + period: 50, + }, + }, }); rebind(IAnalyticsWriter).toConstantValue(NullAnalyticsWriter); rebind(HostContextProviderFactory) diff --git a/components/server/src/workspace/context-service.spec.db.ts b/components/server/src/workspace/context-service.spec.db.ts new file mode 100644 index 00000000000000..97da2d968dea1e --- /dev/null +++ b/components/server/src/workspace/context-service.spec.db.ts @@ -0,0 +1,325 @@ +/** + * Copyright (c) 2023 Gitpod GmbH. All rights reserved. + * Licensed under the GNU Affero General Public License (AGPL). + * See License.AGPL.txt in the project root for license information. + */ + +import { TypeORM } from "@gitpod/gitpod-db/lib"; +import { resetDB } from "@gitpod/gitpod-db/lib/test/reset-db"; +import { + CommitContext, + Organization, + Project, + User, + Workspace as ProtocolWorkspace, + Snapshot, + WorkspaceContext, + StartPrebuildResult, + SnapshotContext, + PrebuiltWorkspaceContext, +} from "@gitpod/gitpod-protocol"; +import * as chai from "chai"; +import { Container } from "inversify"; +import "mocha"; +import { OrganizationService } from "../orgs/organization-service"; +import { createTestContainer } from "../test/service-testing-container-module"; +import { WorkspaceService } from "./workspace-service"; +import { ProjectsService } from "../projects/projects-service"; +import { UserService } from "../user/user-service"; +import { SnapshotService } from "./snapshot-service"; +import { ContextService } from "./context-service"; +import { ContextParser } from "./context-parser-service"; +import { TraceContext } from "@gitpod/gitpod-protocol/lib/util/tracing"; +import { ConfigProvider } from "./config-provider"; +import { PrebuildManager } from "../prebuilds/prebuild-manager"; +import { HostContextProvider } from "../auth/host-context-provider"; +import { AuthProvider } from "../auth/auth-provider"; +import { Experiments } from "@gitpod/gitpod-protocol/lib/experiments/configcat-server"; + +const expect = chai.expect; + +const gitpodEmptyContext = { + ref: "main", + refType: "branch", + path: "", + isFile: false, + repo: "", + repository: { + host: "github.com", + owner: "gitpod-io", + name: "empty", + cloneUrl: "https://github.com/gitpod-io/empty.git", + defaultBranch: "main", + private: false, + }, + normalizedContextURL: "https://github.com/gitpod-io/empty", + revision: "asdf", + title: "gitpod-io/empty - main", +}; + +const SNAPSHOT_BUCKET = "https://gitpod.io/none-bucket"; + +describe("ContextService", async () => { + let container: Container; + let owner: User; + let member: User; + let stranger: User; + let org: Organization; + let org2: Organization; + let project: Project; + let workspace: ProtocolWorkspace; + let snapshot: Snapshot; + let snapshot_stranger: Snapshot; + let prebuild: StartPrebuildResult; + + beforeEach(async () => { + container = createTestContainer(); + Experiments.configureTestingClient({ + centralizedPermissions: true, + }); + container.rebind(ConfigProvider).toConstantValue({ + fetchConfig: () => { + return { + config: { + image: "gitpod/workspace-base", + }, + }; + }, + } as any as ConfigProvider); + + const bindContextParser = () => { + container.rebind(ContextParser).toConstantValue({ + normalizeContextURL: function (contextURL: string): string { + return contextURL + "normalizeContextURL"; + }, + handle: function (ctx: TraceContext, user: User, contextURL: string): Promise { + const url = contextURL.replace("normalizeContextURL", ""); + switch (url) { + case "https://github.com/gitpod-io/empty": + return gitpodEmptyContext as any; + case `open-prebuild/${prebuild.prebuildId}/https://github.com/gitpod-io/empty/tree/main`: + return { + ...gitpodEmptyContext, + openPrebuildID: prebuild.prebuildId, + } as any; + case `snapshot/${snapshot.id}`: { + return { + ...gitpodEmptyContext, + snapshotId: snapshot.id, + snapshotBucketId: SNAPSHOT_BUCKET, + } as any; + } + case `snapshot/${snapshot_stranger.id}`: { + return { + ...gitpodEmptyContext, + snapshotId: snapshot_stranger.id, + snapshotBucketId: SNAPSHOT_BUCKET, + } as any; + } + default: + return { + ref: "master", + } as any; + } + }, + } as any as ContextParser); + }; + + bindContextParser(); + + container.rebind(HostContextProvider).toConstantValue({ + get: () => { + const authProviderId = "Public-GitHub"; + return { + authProvider: { + authProviderId, + info: { + authProviderId, + authProviderType: "GitHub", + }, + }, + services: { + repositoryService: { + installAutomatedPrebuilds: () => {}, + canInstallAutomatedPrebuilds: async () => {}, + }, + repositoryProvider: { + hasReadAccess: async (user: any, owner: string, repo: string) => { + return true; + }, + getBranch: () => { + return { + url: "https://github.com/gitpod-io/empty.git", + name: "main", + htmlUrl: "https://github.com/gitpod-io/empty", + commit: {}, + }; + }, + getRepo: () => { + return { + defaultBranch: "main", + }; + }, + getCommitHistory: () => { + return []; + }, + getCommitInfo: () => { + return undefined; + }, + }, + }, + }; + }, + }); + + const dataInit = async () => { + const userService = container.get(UserService); + // create the owner + owner = await userService.createUser({ + identity: { + authId: "33891423", + authName: "owner", + authProviderId: "Public-GitHub", + }, + }); + + // create the org + const orgService = container.get(OrganizationService); + org = await orgService.createOrganization(owner.id, "my-org"); + + // create and add a member + member = await userService.createUser({ + identity: { + authId: "33891424", + authName: "member", + authProviderId: "Public-GitHub", + }, + }); + const invite = await orgService.getOrCreateInvite(owner.id, org.id); + await orgService.joinOrganization(member.id, invite.id); + + // create a project + const projectService = container.get(ProjectsService); + project = await projectService.createProject( + { + name: "my-project", + slug: "my-project", + teamId: org.id, + cloneUrl: "https://github.com/gitpod-io/empty", + appInstallationId: "noid", + }, + owner, + { + prebuilds: { + enable: true, + branchMatchingPattern: "**", + prebuildInterval: 20, + branchStrategy: "all-branches", + }, + }, + ); + + // create a stranger + stranger = await userService.createUser({ + identity: { + authId: "33891425", + authName: "stranger", + authProviderId: "Public-GitHub", + }, + }); + org2 = await orgService.createOrganization(stranger.id, "stranger-org"); + + // create a workspace + const workspaceService = container.get(WorkspaceService); + workspace = await createTestWorkspace(workspaceService, org, owner, project); + + // take a snapshot + const snapshotService = container.get(SnapshotService); + snapshot = await snapshotService.createSnapshot({ workspaceId: workspace.id }, SNAPSHOT_BUCKET); + + // trigger prebuild + const prebuildManager = container.get(PrebuildManager); + prebuild = await prebuildManager.triggerPrebuild({}, owner, project.id, "main"); + + // create a workspace and snapshot for another user + const anotherWorkspace = await createTestWorkspace(workspaceService, org2, stranger, project); + snapshot_stranger = await snapshotService.createSnapshot( + { workspaceId: anotherWorkspace.id }, + SNAPSHOT_BUCKET, + ); + }; + + await dataInit(); + + bindContextParser(); + }); + + afterEach(async () => { + await resetDB(container.get(TypeORM)); + container.unbindAll(); + }); + + it("should parse normal context", async () => { + const svc = container.get(ContextService); + + const ctx = await svc.parseContext(owner, "https://github.com/gitpod-io/empty", { + projectId: project.id, + organizationId: org.id, + forceDefaultConfig: false, + }); + expect(ctx.project?.id).to.equal(project.id); + expect(CommitContext.is(ctx.context)).to.equal(true); + + expect(ctx.context.ref).to.equal(gitpodEmptyContext.ref); + expect((ctx.context as CommitContext).revision).to.equal(gitpodEmptyContext.revision); + }); + + it("should parser prebuild context", async () => { + const svc = container.get(ContextService); + const ctx = await svc.parseContext( + owner, + `open-prebuild/${prebuild.prebuildId}/https://github.com/gitpod-io/empty/tree/main`, + { + projectId: project.id, + organizationId: org.id, + forceDefaultConfig: false, + }, + ); + expect(ctx.project?.id).to.equal(project.id); + expect(PrebuiltWorkspaceContext.is(ctx.context)).to.equal(true); + }); + + it("should parser snapshot context", async () => { + const svc = container.get(ContextService); + const ctx = await svc.parseContext(owner, `snapshot/${snapshot.id}`, { + projectId: project.id, + organizationId: org.id, + forceDefaultConfig: false, + }); + expect(ctx.project?.id).to.equal(project.id); + expect(SnapshotContext.is(ctx.context)).to.equal(true); + }); + + it("it can start workspace base on stranger's snapshot", async () => { + const svc = container.get(ContextService); + const ctx = await svc.parseContext(owner, `snapshot/${snapshot_stranger.id}`, { + projectId: project.id, + organizationId: org.id, + forceDefaultConfig: false, + }); + expect(ctx.project?.id).to.equal(project.id); + expect(SnapshotContext.is(ctx.context)).to.equal(true); + }); +}); + +async function createTestWorkspace(svc: WorkspaceService, org: Organization, owner: User, project: Project) { + const ws = await svc.createWorkspace( + {}, + owner, + org.id, + project, + gitpodEmptyContext as any as CommitContext, + "github.com/gitpod-io/empty", + ); + return ws; +} From 0ce5ef78703339bd070ab948ec6f08a10a1f6527 Mon Sep 17 00:00:00 2001 From: Huiwen Date: Wed, 22 Nov 2023 16:03:39 +0000 Subject: [PATCH 3/3] Fix rebase build error --- components/server/src/workspace/gitpod-server-impl.ts | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/components/server/src/workspace/gitpod-server-impl.ts b/components/server/src/workspace/gitpod-server-impl.ts index a7ecc7c7662b95..beb0b103309b28 100644 --- a/components/server/src/workspace/gitpod-server-impl.ts +++ b/components/server/src/workspace/gitpod-server-impl.ts @@ -74,11 +74,7 @@ import { AdminModifyRoleOrPermissionRequest, WorkspaceAndInstance, } from "@gitpod/gitpod-protocol/lib/admin-protocol"; -import { - ApplicationError, - ErrorCodes, - UnauthorizedRepositoryAccessError, -} from "@gitpod/gitpod-protocol/lib/messaging/error"; +import { ApplicationError, ErrorCodes } from "@gitpod/gitpod-protocol/lib/messaging/error"; import { log, LogContext } from "@gitpod/gitpod-protocol/lib/util/logging"; import { InterfaceWithTraceContext, @@ -106,8 +102,6 @@ import { UserAuthentication } from "../user/user-authentication"; import { ContextParser } from "./context-parser-service"; import { isClusterMaintenanceError } from "./workspace-starter"; import { HeadlessLogUrls } from "@gitpod/gitpod-protocol/lib/headless-workspace-log"; -import { ConfigProvider } from "./config-provider"; -import { InvalidGitpodYMLError } from "./config-provider"; import { ProjectsService } from "../projects/projects-service"; import { IDEOptions } from "@gitpod/gitpod-protocol/lib/ide-protocol"; import {