From 145584c2898dfe5050a44a2043db68346d9d6656 Mon Sep 17 00:00:00 2001 From: Huiwen Date: Tue, 7 Nov 2023 06:35:33 +0000 Subject: [PATCH 01/11] [papi] add watch workspace status api --- .../src/service/json-rpc-workspace-client.ts | 47 +- .../src/generate-async-generator.ts | 61 ++ .../public-api/gitpod/v1/workspace.proto | 17 + .../go/v1/v1connect/workspace.connect.go | 30 +- components/public-api/go/v1/workspace.pb.go | 606 +++++++++++------- .../public-api/go/v1/workspace_grpc.pb.go | 72 ++- .../src/gitpod/v1/workspace_connect.ts | 15 +- .../typescript/src/gitpod/v1/workspace_pb.ts | 80 +++ components/server/package.json | 2 +- .../server/src/api/workspace-service-api.ts | 31 + .../server/src/workspace/workspace-service.ts | 13 + package.json | 2 +- yarn.lock | 12 + 13 files changed, 752 insertions(+), 236 deletions(-) create mode 100644 components/gitpod-protocol/src/generate-async-generator.ts diff --git a/components/dashboard/src/service/json-rpc-workspace-client.ts b/components/dashboard/src/service/json-rpc-workspace-client.ts index 43414a878ff685..ecef7df798aa1d 100644 --- a/components/dashboard/src/service/json-rpc-workspace-client.ts +++ b/components/dashboard/src/service/json-rpc-workspace-client.ts @@ -4,12 +4,14 @@ * See License.AGPL.txt in the project root for license information. */ -import { Code, ConnectError, PromiseClient } from "@connectrpc/connect"; +import { CallOptions, Code, ConnectError, PromiseClient } from "@connectrpc/connect"; import { PartialMessage } from "@bufbuild/protobuf"; import { WorkspaceService } from "@gitpod/public-api/lib/gitpod/v1/workspace_connect"; import { GetWorkspaceRequest, GetWorkspaceResponse } from "@gitpod/public-api/lib/gitpod/v1/workspace_pb"; import { converter } from "./public-api"; import { getGitpodService } from "./service"; +import { generateAsyncGenerator } from "@gitpod/gitpod-protocol/lib/generate-async-generator"; +import { WorkspaceInstance } from "@gitpod/gitpod-protocol"; export class JsonRpcWorkspaceClient implements PromiseClient { async getWorkspace(request: PartialMessage): Promise { @@ -22,4 +24,47 @@ export class JsonRpcWorkspaceClient implements PromiseClient, + options?: CallOptions, + ): AsyncIterable { + if (!options?.signal) { + throw new ConnectError("signal is required", Code.InvalidArgument); + } + const it = generateAsyncGenerator( + (sink) => { + const dispose = getGitpodService().registerClient({ + onInstanceUpdate: (instance) => { + sink.next(instance); + }, + }); + return dispose.dispose; + }, + { signal: options.signal }, + ); + if (request.workspaceId) { + const resp = await this.getWorkspace({ id: request.workspaceId }); + if (resp.item?.status) { + const response = new WatchWorkspaceStatusResponse(); + response.status = resp.item.status; + yield response; + } + } + for await (const item of it) { + if (!item) { + continue; + } + if (request.workspaceId && item.workspaceId !== request.workspaceId) { + continue; + } + const status = converter.toWorkspace(item).status; + if (!status) { + continue; + } + const response = new WatchWorkspaceStatusResponse(); + response.status = status; + yield response; + } + } } diff --git a/components/gitpod-protocol/src/generate-async-generator.ts b/components/gitpod-protocol/src/generate-async-generator.ts new file mode 100644 index 00000000000000..8c85697e3b472d --- /dev/null +++ b/components/gitpod-protocol/src/generate-async-generator.ts @@ -0,0 +1,61 @@ +/** + * 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. + */ + +type Sink = { + next: (value: T | void) => void; +}; + +/** + * Generates an asynchronous generator that yields values based on the provided setup function. + * + * the setup function that takes a sink and returns a cleanup function. + * setup sink object has a `next` method that accepts a value to be pushed to the generator. + */ +export async function* generateAsyncGenerator( + setup: (sink: Sink) => () => void, + opts: { signal: AbortSignal }, +): AsyncGenerator { + const queue: T[] = []; + + let resolveNext: ((value: T | void) => void) | null = null; + + const sink: Sink = { + next: (value: T | void) => { + if (resolveNext) { + resolveNext(value); + resolveNext = null; + } else { + if (value) { + queue.push(value); + } + } + }, + }; + + let isStopped = false; + opts.signal.addEventListener("abort", () => { + isStopped = true; + sink.next(); + }); + + const cleanup = setup(sink); + + try { + while (!isStopped) { + if (queue.length) { + yield queue.shift(); + } else { + yield new Promise((resolve) => { + resolveNext = resolve; + }); + } + } + // ignore error since code in `try` scope will not throw an error + // unless caller use it.throw, then it will throw to itself + } finally { + cleanup(); + } +} diff --git a/components/public-api/gitpod/v1/workspace.proto b/components/public-api/gitpod/v1/workspace.proto index 91ed2e61898e4c..aec1089114f4e7 100644 --- a/components/public-api/gitpod/v1/workspace.proto +++ b/components/public-api/gitpod/v1/workspace.proto @@ -12,12 +12,29 @@ service WorkspaceService { // +return NOT_FOUND User does not have access to a workspace with the given // ID +return NOT_FOUND Workspace does not exist rpc GetWorkspace(GetWorkspaceRequest) returns (GetWorkspaceResponse) {} + + // WatchWorkspaceStatus watchs the workspaces status changes + // + // ID +return NOT_FOUND Workspace does not exist + rpc WatchWorkspaceStatus(WatchWorkspaceStatusRequest) returns (stream WatchWorkspaceStatusResponse) {} } message GetWorkspaceRequest { string id = 1; } message GetWorkspaceResponse { Workspace item = 1; } +message WatchWorkspaceStatusRequest { + // workspace_id specifies the workspace to watch + // + // +optional if empty then watch all workspaces + optional string workspace_id = 1; +} + +message WatchWorkspaceStatusResponse { + // item is the workspace that was changed + WorkspaceStatus status = 1; +} + // +resource get workspace message Workspace { string id = 1; diff --git a/components/public-api/go/v1/v1connect/workspace.connect.go b/components/public-api/go/v1/v1connect/workspace.connect.go index 8d08e6ef2fe707..41696bec989387 100644 --- a/components/public-api/go/v1/v1connect/workspace.connect.go +++ b/components/public-api/go/v1/v1connect/workspace.connect.go @@ -36,6 +36,10 @@ 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 + // + // ID +return NOT_FOUND Workspace does not exist + WatchWorkspaceStatus(context.Context, *connect_go.Request[v1.WatchWorkspaceStatusRequest]) (*connect_go.ServerStreamForClient[v1.WatchWorkspaceStatusResponse], error) } // NewWorkspaceServiceClient constructs a client for the gitpod.v1.WorkspaceService service. By @@ -53,12 +57,18 @@ func NewWorkspaceServiceClient(httpClient connect_go.HTTPClient, baseURL string, baseURL+"/gitpod.v1.WorkspaceService/GetWorkspace", opts..., ), + watchWorkspaceStatus: connect_go.NewClient[v1.WatchWorkspaceStatusRequest, v1.WatchWorkspaceStatusResponse]( + httpClient, + baseURL+"/gitpod.v1.WorkspaceService/WatchWorkspaceStatus", + opts..., + ), } } // workspaceServiceClient implements WorkspaceServiceClient. type workspaceServiceClient struct { - getWorkspace *connect_go.Client[v1.GetWorkspaceRequest, v1.GetWorkspaceResponse] + getWorkspace *connect_go.Client[v1.GetWorkspaceRequest, v1.GetWorkspaceResponse] + watchWorkspaceStatus *connect_go.Client[v1.WatchWorkspaceStatusRequest, v1.WatchWorkspaceStatusResponse] } // GetWorkspace calls gitpod.v1.WorkspaceService.GetWorkspace. @@ -66,6 +76,11 @@ func (c *workspaceServiceClient) GetWorkspace(ctx context.Context, req *connect_ return c.getWorkspace.CallUnary(ctx, req) } +// WatchWorkspaceStatus calls gitpod.v1.WorkspaceService.WatchWorkspaceStatus. +func (c *workspaceServiceClient) WatchWorkspaceStatus(ctx context.Context, req *connect_go.Request[v1.WatchWorkspaceStatusRequest]) (*connect_go.ServerStreamForClient[v1.WatchWorkspaceStatusResponse], error) { + return c.watchWorkspaceStatus.CallServerStream(ctx, req) +} + // WorkspaceServiceHandler is an implementation of the gitpod.v1.WorkspaceService service. type WorkspaceServiceHandler interface { // GetWorkspace returns a single workspace. @@ -73,6 +88,10 @@ 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 + // + // ID +return NOT_FOUND Workspace does not exist + WatchWorkspaceStatus(context.Context, *connect_go.Request[v1.WatchWorkspaceStatusRequest], *connect_go.ServerStream[v1.WatchWorkspaceStatusResponse]) error } // NewWorkspaceServiceHandler builds an HTTP handler from the service implementation. It returns the @@ -87,6 +106,11 @@ func NewWorkspaceServiceHandler(svc WorkspaceServiceHandler, opts ...connect_go. svc.GetWorkspace, opts..., )) + mux.Handle("/gitpod.v1.WorkspaceService/WatchWorkspaceStatus", connect_go.NewServerStreamHandler( + "/gitpod.v1.WorkspaceService/WatchWorkspaceStatus", + svc.WatchWorkspaceStatus, + opts..., + )) return "/gitpod.v1.WorkspaceService/", mux } @@ -96,3 +120,7 @@ type UnimplementedWorkspaceServiceHandler struct{} func (UnimplementedWorkspaceServiceHandler) GetWorkspace(context.Context, *connect_go.Request[v1.GetWorkspaceRequest]) (*connect_go.Response[v1.GetWorkspaceResponse], error) { return nil, connect_go.NewError(connect_go.CodeUnimplemented, errors.New("gitpod.v1.WorkspaceService.GetWorkspace is not implemented")) } + +func (UnimplementedWorkspaceServiceHandler) WatchWorkspaceStatus(context.Context, *connect_go.Request[v1.WatchWorkspaceStatusRequest], *connect_go.ServerStream[v1.WatchWorkspaceStatusResponse]) error { + return connect_go.NewError(connect_go.CodeUnimplemented, errors.New("gitpod.v1.WorkspaceService.WatchWorkspaceStatus is not implemented")) +} diff --git a/components/public-api/go/v1/workspace.pb.go b/components/public-api/go/v1/workspace.pb.go index c7f211182213a5..0f224aab750655 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{5, 0} + return file_gitpod_v1_workspace_proto_rawDescGZIP(), []int{7, 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{5, 1} + return file_gitpod_v1_workspace_proto_rawDescGZIP(), []int{7, 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{7, 0} + return file_gitpod_v1_workspace_proto_rawDescGZIP(), []int{9, 0} } type GetWorkspaceRequest struct { @@ -377,6 +377,104 @@ func (x *GetWorkspaceResponse) GetItem() *Workspace { return nil } +type WatchWorkspaceStatusRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // workspace_id specifies the workspace to watch + // + // +optional if empty then watch all workspaces + WorkspaceId *string `protobuf:"bytes,1,opt,name=workspace_id,json=workspaceId,proto3,oneof" json:"workspace_id,omitempty"` +} + +func (x *WatchWorkspaceStatusRequest) Reset() { + *x = WatchWorkspaceStatusRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_gitpod_v1_workspace_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *WatchWorkspaceStatusRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*WatchWorkspaceStatusRequest) ProtoMessage() {} + +func (x *WatchWorkspaceStatusRequest) ProtoReflect() protoreflect.Message { + mi := &file_gitpod_v1_workspace_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 WatchWorkspaceStatusRequest.ProtoReflect.Descriptor instead. +func (*WatchWorkspaceStatusRequest) Descriptor() ([]byte, []int) { + return file_gitpod_v1_workspace_proto_rawDescGZIP(), []int{2} +} + +func (x *WatchWorkspaceStatusRequest) GetWorkspaceId() string { + if x != nil && x.WorkspaceId != nil { + return *x.WorkspaceId + } + return "" +} + +type WatchWorkspaceStatusResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // item is the workspace that was changed + Status *WorkspaceStatus `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"` +} + +func (x *WatchWorkspaceStatusResponse) Reset() { + *x = WatchWorkspaceStatusResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_gitpod_v1_workspace_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *WatchWorkspaceStatusResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*WatchWorkspaceStatusResponse) ProtoMessage() {} + +func (x *WatchWorkspaceStatusResponse) ProtoReflect() protoreflect.Message { + mi := &file_gitpod_v1_workspace_proto_msgTypes[3] + 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 WatchWorkspaceStatusResponse.ProtoReflect.Descriptor instead. +func (*WatchWorkspaceStatusResponse) Descriptor() ([]byte, []int) { + return file_gitpod_v1_workspace_proto_rawDescGZIP(), []int{3} +} + +func (x *WatchWorkspaceStatusResponse) GetStatus() *WorkspaceStatus { + if x != nil { + return x.Status + } + return nil +} + // +resource get workspace type Workspace struct { state protoimpl.MessageState @@ -426,7 +524,7 @@ type Workspace struct { func (x *Workspace) Reset() { *x = Workspace{} if protoimpl.UnsafeEnabled { - mi := &file_gitpod_v1_workspace_proto_msgTypes[2] + mi := &file_gitpod_v1_workspace_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -439,7 +537,7 @@ func (x *Workspace) String() string { func (*Workspace) ProtoMessage() {} func (x *Workspace) ProtoReflect() protoreflect.Message { - mi := &file_gitpod_v1_workspace_proto_msgTypes[2] + mi := &file_gitpod_v1_workspace_proto_msgTypes[4] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -452,7 +550,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{2} + return file_gitpod_v1_workspace_proto_rawDescGZIP(), []int{4} } func (x *Workspace) GetId() string { @@ -571,7 +669,7 @@ type WorkspaceStatus struct { func (x *WorkspaceStatus) Reset() { *x = WorkspaceStatus{} if protoimpl.UnsafeEnabled { - mi := &file_gitpod_v1_workspace_proto_msgTypes[3] + mi := &file_gitpod_v1_workspace_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -584,7 +682,7 @@ func (x *WorkspaceStatus) String() string { func (*WorkspaceStatus) ProtoMessage() {} func (x *WorkspaceStatus) ProtoReflect() protoreflect.Message { - mi := &file_gitpod_v1_workspace_proto_msgTypes[3] + mi := &file_gitpod_v1_workspace_proto_msgTypes[5] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -597,7 +695,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{3} + return file_gitpod_v1_workspace_proto_rawDescGZIP(), []int{5} } func (x *WorkspaceStatus) GetPhase() *WorkspacePhase { @@ -672,7 +770,7 @@ type WorkspaceConditions struct { func (x *WorkspaceConditions) Reset() { *x = WorkspaceConditions{} if protoimpl.UnsafeEnabled { - mi := &file_gitpod_v1_workspace_proto_msgTypes[4] + mi := &file_gitpod_v1_workspace_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -685,7 +783,7 @@ func (x *WorkspaceConditions) String() string { func (*WorkspaceConditions) ProtoMessage() {} func (x *WorkspaceConditions) ProtoReflect() protoreflect.Message { - mi := &file_gitpod_v1_workspace_proto_msgTypes[4] + mi := &file_gitpod_v1_workspace_proto_msgTypes[6] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -698,7 +796,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{4} + return file_gitpod_v1_workspace_proto_rawDescGZIP(), []int{6} } func (x *WorkspaceConditions) GetFailed() string { @@ -733,7 +831,7 @@ type WorkspacePort struct { func (x *WorkspacePort) Reset() { *x = WorkspacePort{} if protoimpl.UnsafeEnabled { - mi := &file_gitpod_v1_workspace_proto_msgTypes[5] + mi := &file_gitpod_v1_workspace_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -746,7 +844,7 @@ func (x *WorkspacePort) String() string { func (*WorkspacePort) ProtoMessage() {} func (x *WorkspacePort) ProtoReflect() protoreflect.Message { - mi := &file_gitpod_v1_workspace_proto_msgTypes[5] + mi := &file_gitpod_v1_workspace_proto_msgTypes[7] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -759,7 +857,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{5} + return file_gitpod_v1_workspace_proto_rawDescGZIP(), []int{7} } func (x *WorkspacePort) GetPort() uint64 { @@ -821,7 +919,7 @@ type WorkspaceGitStatus struct { func (x *WorkspaceGitStatus) Reset() { *x = WorkspaceGitStatus{} if protoimpl.UnsafeEnabled { - mi := &file_gitpod_v1_workspace_proto_msgTypes[6] + mi := &file_gitpod_v1_workspace_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -834,7 +932,7 @@ func (x *WorkspaceGitStatus) String() string { func (*WorkspaceGitStatus) ProtoMessage() {} func (x *WorkspaceGitStatus) ProtoReflect() protoreflect.Message { - mi := &file_gitpod_v1_workspace_proto_msgTypes[6] + mi := &file_gitpod_v1_workspace_proto_msgTypes[8] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -847,7 +945,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{6} + return file_gitpod_v1_workspace_proto_rawDescGZIP(), []int{8} } func (x *WorkspaceGitStatus) GetCloneUrl() string { @@ -925,7 +1023,7 @@ type WorkspacePhase struct { func (x *WorkspacePhase) Reset() { *x = WorkspacePhase{} if protoimpl.UnsafeEnabled { - mi := &file_gitpod_v1_workspace_proto_msgTypes[7] + mi := &file_gitpod_v1_workspace_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -938,7 +1036,7 @@ func (x *WorkspacePhase) String() string { func (*WorkspacePhase) ProtoMessage() {} func (x *WorkspacePhase) ProtoReflect() protoreflect.Message { - mi := &file_gitpod_v1_workspace_proto_msgTypes[7] + mi := &file_gitpod_v1_workspace_proto_msgTypes[9] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -951,7 +1049,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{7} + return file_gitpod_v1_workspace_proto_rawDescGZIP(), []int{9} } func (x *WorkspacePhase) GetName() WorkspacePhase_Phase { @@ -980,7 +1078,7 @@ type EditorReference struct { func (x *EditorReference) Reset() { *x = EditorReference{} if protoimpl.UnsafeEnabled { - mi := &file_gitpod_v1_workspace_proto_msgTypes[8] + mi := &file_gitpod_v1_workspace_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -993,7 +1091,7 @@ func (x *EditorReference) String() string { func (*EditorReference) ProtoMessage() {} func (x *EditorReference) ProtoReflect() protoreflect.Message { - mi := &file_gitpod_v1_workspace_proto_msgTypes[8] + mi := &file_gitpod_v1_workspace_proto_msgTypes[10] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1006,7 +1104,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{8} + return file_gitpod_v1_workspace_proto_rawDescGZIP(), []int{10} } func (x *EditorReference) GetName() string { @@ -1035,7 +1133,7 @@ type WorkspaceEnvironmentVariable struct { func (x *WorkspaceEnvironmentVariable) Reset() { *x = WorkspaceEnvironmentVariable{} if protoimpl.UnsafeEnabled { - mi := &file_gitpod_v1_workspace_proto_msgTypes[9] + mi := &file_gitpod_v1_workspace_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1048,7 +1146,7 @@ func (x *WorkspaceEnvironmentVariable) String() string { func (*WorkspaceEnvironmentVariable) ProtoMessage() {} func (x *WorkspaceEnvironmentVariable) ProtoReflect() protoreflect.Message { - mi := &file_gitpod_v1_workspace_proto_msgTypes[9] + mi := &file_gitpod_v1_workspace_proto_msgTypes[11] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1061,7 +1159,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{9} + return file_gitpod_v1_workspace_proto_rawDescGZIP(), []int{11} } func (x *WorkspaceEnvironmentVariable) GetName() string { @@ -1092,170 +1190,188 @@ var file_gitpod_v1_workspace_proto_rawDesc = []byte{ 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x28, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 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, 0x04, 0x69, 0x74, 0x65, 0x6d, - 0x22, 0xb8, 0x04, 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, + 0x22, 0x56, 0x0a, 0x1b, 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, 0x12, + 0x26, 0x0a, 0x0c, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0b, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, + 0x63, 0x65, 0x49, 0x64, 0x88, 0x01, 0x01, 0x42, 0x0f, 0x0a, 0x0d, 0x5f, 0x77, 0x6f, 0x72, 0x6b, + 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x22, 0x52, 0x0a, 0x1c, 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, 0x12, 0x32, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x18, 0x01, 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, 0x22, 0xb8, 0x04, 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, 0x1b, 0x0a, 0x06, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x18, 0x08, 0x20, 0x01, + 0x28, 0x09, 0x48, 0x00, 0x52, 0x06, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x12, + 0x2c, 0x0a, 0x0f, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x63, 0x6c, 0x61, + 0x73, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x48, 0x01, 0x52, 0x0e, 0x77, 0x6f, 0x72, 0x6b, + 0x73, 0x70, 0x61, 0x63, 0x65, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x88, 0x01, 0x01, 0x12, 0x37, 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, 0x48, 0x02, 0x52, 0x06, 0x65, 0x64, 0x69, + 0x74, 0x6f, 0x72, 0x88, 0x01, 0x01, 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, 0x24, 0x0a, 0x0b, 0x70, 0x72, 0x65, 0x62, 0x75, + 0x69, 0x6c, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x48, 0x03, 0x52, 0x0a, + 0x70, 0x72, 0x65, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x49, 0x64, 0x88, 0x01, 0x01, 0x42, 0x09, 0x0a, + 0x07, 0x5f, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x42, 0x12, 0x0a, 0x10, 0x5f, 0x77, 0x6f, 0x72, + 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x42, 0x09, 0x0a, 0x07, + 0x5f, 0x65, 0x64, 0x69, 0x74, 0x6f, 0x72, 0x42, 0x0e, 0x0a, 0x0c, 0x5f, 0x70, 0x72, 0x65, 0x62, + 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x69, 0x64, 0x22, 0x9a, 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, 0x1d, 0x0a, 0x07, + 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, + 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x88, 0x01, 0x01, 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, 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, 0x1b, 0x0a, 0x06, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, - 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x06, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, - 0x88, 0x01, 0x01, 0x12, 0x2c, 0x0a, 0x0f, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, - 0x5f, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x48, 0x01, 0x52, 0x0e, - 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x88, 0x01, - 0x01, 0x12, 0x37, 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, 0x48, 0x02, 0x52, - 0x06, 0x65, 0x64, 0x69, 0x74, 0x6f, 0x72, 0x88, 0x01, 0x01, 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, 0x24, 0x0a, 0x0b, 0x70, - 0x72, 0x65, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, - 0x48, 0x03, 0x52, 0x0a, 0x70, 0x72, 0x65, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x49, 0x64, 0x88, 0x01, - 0x01, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x42, 0x12, 0x0a, 0x10, - 0x5f, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x63, 0x6c, 0x61, 0x73, 0x73, - 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x65, 0x64, 0x69, 0x74, 0x6f, 0x72, 0x42, 0x0e, 0x0a, 0x0c, 0x5f, - 0x70, 0x72, 0x65, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x69, 0x64, 0x22, 0x9a, 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, 0x1d, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x48, 0x00, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x88, 0x01, 0x01, 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, 0x42, 0x0a, 0x0a, 0x08, - 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x68, 0x0a, 0x13, 0x57, 0x6f, 0x72, 0x6b, - 0x73, 0x70, 0x61, 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, - 0x1b, 0x0a, 0x06, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x48, - 0x00, 0x52, 0x06, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x88, 0x01, 0x01, 0x12, 0x1d, 0x0a, 0x07, - 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x01, 0x52, - 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x88, 0x01, 0x01, 0x42, 0x09, 0x0a, 0x07, 0x5f, - 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 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, 0x57, 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, - 0x19, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, - 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x88, 0x01, 0x01, 0x42, 0x08, 0x0a, 0x06, 0x5f, 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, 0x65, 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, 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, + 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, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x6d, 0x65, 0x73, + 0x73, 0x61, 0x67, 0x65, 0x22, 0x68, 0x0a, 0x13, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, + 0x65, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1b, 0x0a, 0x06, 0x66, + 0x61, 0x69, 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x06, 0x66, + 0x61, 0x69, 0x6c, 0x65, 0x64, 0x88, 0x01, 0x01, 0x12, 0x1d, 0x0a, 0x07, 0x74, 0x69, 0x6d, 0x65, + 0x6f, 0x75, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x01, 0x52, 0x07, 0x74, 0x69, 0x6d, + 0x65, 0x6f, 0x75, 0x74, 0x88, 0x01, 0x01, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x66, 0x61, 0x69, 0x6c, + 0x65, 0x64, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 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, 0x57, 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, 0x19, 0x0a, 0x05, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x05, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x88, 0x01, 0x01, 0x42, 0x08, 0x0a, 0x06, 0x5f, 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, 0xd2, 0x01, 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, 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 ( @@ -1271,7 +1387,7 @@ 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, 10) +var file_gitpod_v1_workspace_proto_msgTypes = make([]protoimpl.MessageInfo, 12) var file_gitpod_v1_workspace_proto_goTypes = []interface{}{ (AdmissionLevel)(0), // 0: gitpod.v1.AdmissionLevel (WorkspacePort_Policy)(0), // 1: gitpod.v1.WorkspacePort.Policy @@ -1279,37 +1395,42 @@ var file_gitpod_v1_workspace_proto_goTypes = []interface{}{ (WorkspacePhase_Phase)(0), // 3: gitpod.v1.WorkspacePhase.Phase (*GetWorkspaceRequest)(nil), // 4: gitpod.v1.GetWorkspaceRequest (*GetWorkspaceResponse)(nil), // 5: gitpod.v1.GetWorkspaceResponse - (*Workspace)(nil), // 6: gitpod.v1.Workspace - (*WorkspaceStatus)(nil), // 7: gitpod.v1.WorkspaceStatus - (*WorkspaceConditions)(nil), // 8: gitpod.v1.WorkspaceConditions - (*WorkspacePort)(nil), // 9: gitpod.v1.WorkspacePort - (*WorkspaceGitStatus)(nil), // 10: gitpod.v1.WorkspaceGitStatus - (*WorkspacePhase)(nil), // 11: gitpod.v1.WorkspacePhase - (*EditorReference)(nil), // 12: gitpod.v1.EditorReference - (*WorkspaceEnvironmentVariable)(nil), // 13: gitpod.v1.WorkspaceEnvironmentVariable - (*timestamppb.Timestamp)(nil), // 14: google.protobuf.Timestamp + (*WatchWorkspaceStatusRequest)(nil), // 6: gitpod.v1.WatchWorkspaceStatusRequest + (*WatchWorkspaceStatusResponse)(nil), // 7: gitpod.v1.WatchWorkspaceStatusResponse + (*Workspace)(nil), // 8: gitpod.v1.Workspace + (*WorkspaceStatus)(nil), // 9: gitpod.v1.WorkspaceStatus + (*WorkspaceConditions)(nil), // 10: gitpod.v1.WorkspaceConditions + (*WorkspacePort)(nil), // 11: gitpod.v1.WorkspacePort + (*WorkspaceGitStatus)(nil), // 12: gitpod.v1.WorkspaceGitStatus + (*WorkspacePhase)(nil), // 13: gitpod.v1.WorkspacePhase + (*EditorReference)(nil), // 14: gitpod.v1.EditorReference + (*WorkspaceEnvironmentVariable)(nil), // 15: gitpod.v1.WorkspaceEnvironmentVariable + (*timestamppb.Timestamp)(nil), // 16: google.protobuf.Timestamp } var file_gitpod_v1_workspace_proto_depIdxs = []int32{ - 6, // 0: gitpod.v1.GetWorkspaceResponse.item:type_name -> gitpod.v1.Workspace - 7, // 1: gitpod.v1.Workspace.status:type_name -> gitpod.v1.WorkspaceStatus - 13, // 2: gitpod.v1.Workspace.additional_environment_variables:type_name -> gitpod.v1.WorkspaceEnvironmentVariable - 12, // 3: gitpod.v1.Workspace.editor:type_name -> gitpod.v1.EditorReference - 11, // 4: gitpod.v1.WorkspaceStatus.phase:type_name -> gitpod.v1.WorkspacePhase - 10, // 5: gitpod.v1.WorkspaceStatus.git_status:type_name -> gitpod.v1.WorkspaceGitStatus - 9, // 6: gitpod.v1.WorkspaceStatus.ports:type_name -> gitpod.v1.WorkspacePort - 0, // 7: gitpod.v1.WorkspaceStatus.admission:type_name -> gitpod.v1.AdmissionLevel - 8, // 8: gitpod.v1.WorkspaceStatus.conditions:type_name -> gitpod.v1.WorkspaceConditions - 1, // 9: gitpod.v1.WorkspacePort.policy:type_name -> gitpod.v1.WorkspacePort.Policy - 2, // 10: gitpod.v1.WorkspacePort.protocol:type_name -> gitpod.v1.WorkspacePort.Protocol - 3, // 11: gitpod.v1.WorkspacePhase.name:type_name -> gitpod.v1.WorkspacePhase.Phase - 14, // 12: gitpod.v1.WorkspacePhase.last_transition_time:type_name -> google.protobuf.Timestamp - 4, // 13: gitpod.v1.WorkspaceService.GetWorkspace:input_type -> gitpod.v1.GetWorkspaceRequest - 5, // 14: gitpod.v1.WorkspaceService.GetWorkspace:output_type -> gitpod.v1.GetWorkspaceResponse - 14, // [14:15] is the sub-list for method output_type - 13, // [13:14] is the sub-list for method input_type - 13, // [13:13] is the sub-list for extension type_name - 13, // [13:13] is the sub-list for extension extendee - 0, // [0:13] is the sub-list for field type_name + 8, // 0: gitpod.v1.GetWorkspaceResponse.item:type_name -> gitpod.v1.Workspace + 9, // 1: gitpod.v1.WatchWorkspaceStatusResponse.status:type_name -> gitpod.v1.WorkspaceStatus + 9, // 2: gitpod.v1.Workspace.status:type_name -> gitpod.v1.WorkspaceStatus + 15, // 3: gitpod.v1.Workspace.additional_environment_variables:type_name -> gitpod.v1.WorkspaceEnvironmentVariable + 14, // 4: gitpod.v1.Workspace.editor:type_name -> gitpod.v1.EditorReference + 13, // 5: gitpod.v1.WorkspaceStatus.phase:type_name -> gitpod.v1.WorkspacePhase + 12, // 6: gitpod.v1.WorkspaceStatus.git_status:type_name -> gitpod.v1.WorkspaceGitStatus + 11, // 7: gitpod.v1.WorkspaceStatus.ports:type_name -> gitpod.v1.WorkspacePort + 0, // 8: gitpod.v1.WorkspaceStatus.admission:type_name -> gitpod.v1.AdmissionLevel + 10, // 9: gitpod.v1.WorkspaceStatus.conditions:type_name -> gitpod.v1.WorkspaceConditions + 1, // 10: gitpod.v1.WorkspacePort.policy:type_name -> gitpod.v1.WorkspacePort.Policy + 2, // 11: gitpod.v1.WorkspacePort.protocol:type_name -> gitpod.v1.WorkspacePort.Protocol + 3, // 12: gitpod.v1.WorkspacePhase.name:type_name -> gitpod.v1.WorkspacePhase.Phase + 16, // 13: gitpod.v1.WorkspacePhase.last_transition_time:type_name -> google.protobuf.Timestamp + 4, // 14: gitpod.v1.WorkspaceService.GetWorkspace:input_type -> gitpod.v1.GetWorkspaceRequest + 6, // 15: gitpod.v1.WorkspaceService.WatchWorkspaceStatus:input_type -> gitpod.v1.WatchWorkspaceStatusRequest + 5, // 16: gitpod.v1.WorkspaceService.GetWorkspace:output_type -> gitpod.v1.GetWorkspaceResponse + 7, // 17: gitpod.v1.WorkspaceService.WatchWorkspaceStatus:output_type -> gitpod.v1.WatchWorkspaceStatusResponse + 16, // [16:18] is the sub-list for method output_type + 14, // [14:16] is the sub-list for method input_type + 14, // [14:14] is the sub-list for extension type_name + 14, // [14:14] is the sub-list for extension extendee + 0, // [0:14] is the sub-list for field type_name } func init() { file_gitpod_v1_workspace_proto_init() } @@ -1343,7 +1464,7 @@ func file_gitpod_v1_workspace_proto_init() { } } file_gitpod_v1_workspace_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Workspace); i { + switch v := v.(*WatchWorkspaceStatusRequest); i { case 0: return &v.state case 1: @@ -1355,7 +1476,7 @@ func file_gitpod_v1_workspace_proto_init() { } } file_gitpod_v1_workspace_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*WorkspaceStatus); i { + switch v := v.(*WatchWorkspaceStatusResponse); i { case 0: return &v.state case 1: @@ -1367,7 +1488,7 @@ func file_gitpod_v1_workspace_proto_init() { } } file_gitpod_v1_workspace_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*WorkspaceConditions); i { + switch v := v.(*Workspace); i { case 0: return &v.state case 1: @@ -1379,7 +1500,7 @@ func file_gitpod_v1_workspace_proto_init() { } } file_gitpod_v1_workspace_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*WorkspacePort); i { + switch v := v.(*WorkspaceStatus); i { case 0: return &v.state case 1: @@ -1391,7 +1512,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.(*WorkspaceGitStatus); i { + switch v := v.(*WorkspaceConditions); i { case 0: return &v.state case 1: @@ -1403,7 +1524,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.(*WorkspacePhase); i { + switch v := v.(*WorkspacePort); i { case 0: return &v.state case 1: @@ -1415,7 +1536,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.(*EditorReference); i { + switch v := v.(*WorkspaceGitStatus); i { case 0: return &v.state case 1: @@ -1427,6 +1548,30 @@ func file_gitpod_v1_workspace_proto_init() { } } file_gitpod_v1_workspace_proto_msgTypes[9].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[10].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[11].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*WorkspaceEnvironmentVariable); i { case 0: return &v.state @@ -1440,16 +1585,17 @@ func file_gitpod_v1_workspace_proto_init() { } } file_gitpod_v1_workspace_proto_msgTypes[2].OneofWrappers = []interface{}{} - file_gitpod_v1_workspace_proto_msgTypes[3].OneofWrappers = []interface{}{} file_gitpod_v1_workspace_proto_msgTypes[4].OneofWrappers = []interface{}{} - file_gitpod_v1_workspace_proto_msgTypes[9].OneofWrappers = []interface{}{} + file_gitpod_v1_workspace_proto_msgTypes[5].OneofWrappers = []interface{}{} + file_gitpod_v1_workspace_proto_msgTypes[6].OneofWrappers = []interface{}{} + file_gitpod_v1_workspace_proto_msgTypes[11].OneofWrappers = []interface{}{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_gitpod_v1_workspace_proto_rawDesc, NumEnums: 4, - NumMessages: 10, + NumMessages: 12, 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 dbd16b75da5f6b..777e3567814415 100644 --- a/components/public-api/go/v1/workspace_grpc.pb.go +++ b/components/public-api/go/v1/workspace_grpc.pb.go @@ -31,6 +31,10 @@ 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 + // + // ID +return NOT_FOUND Workspace does not exist + WatchWorkspaceStatus(ctx context.Context, in *WatchWorkspaceStatusRequest, opts ...grpc.CallOption) (WorkspaceService_WatchWorkspaceStatusClient, error) } type workspaceServiceClient struct { @@ -50,6 +54,38 @@ func (c *workspaceServiceClient) GetWorkspace(ctx context.Context, in *GetWorksp return out, nil } +func (c *workspaceServiceClient) WatchWorkspaceStatus(ctx context.Context, in *WatchWorkspaceStatusRequest, opts ...grpc.CallOption) (WorkspaceService_WatchWorkspaceStatusClient, error) { + stream, err := c.cc.NewStream(ctx, &WorkspaceService_ServiceDesc.Streams[0], "/gitpod.v1.WorkspaceService/WatchWorkspaceStatus", opts...) + if err != nil { + return nil, err + } + x := &workspaceServiceWatchWorkspaceStatusClient{stream} + if err := x.ClientStream.SendMsg(in); err != nil { + return nil, err + } + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + return x, nil +} + +type WorkspaceService_WatchWorkspaceStatusClient interface { + Recv() (*WatchWorkspaceStatusResponse, error) + grpc.ClientStream +} + +type workspaceServiceWatchWorkspaceStatusClient struct { + grpc.ClientStream +} + +func (x *workspaceServiceWatchWorkspaceStatusClient) Recv() (*WatchWorkspaceStatusResponse, error) { + m := new(WatchWorkspaceStatusResponse) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + // WorkspaceServiceServer is the server API for WorkspaceService service. // All implementations must embed UnimplementedWorkspaceServiceServer // for forward compatibility @@ -59,6 +95,10 @@ 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 + // + // ID +return NOT_FOUND Workspace does not exist + WatchWorkspaceStatus(*WatchWorkspaceStatusRequest, WorkspaceService_WatchWorkspaceStatusServer) error mustEmbedUnimplementedWorkspaceServiceServer() } @@ -69,6 +109,9 @@ type UnimplementedWorkspaceServiceServer struct { func (UnimplementedWorkspaceServiceServer) GetWorkspace(context.Context, *GetWorkspaceRequest) (*GetWorkspaceResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GetWorkspace not implemented") } +func (UnimplementedWorkspaceServiceServer) WatchWorkspaceStatus(*WatchWorkspaceStatusRequest, WorkspaceService_WatchWorkspaceStatusServer) error { + return status.Errorf(codes.Unimplemented, "method WatchWorkspaceStatus not implemented") +} func (UnimplementedWorkspaceServiceServer) mustEmbedUnimplementedWorkspaceServiceServer() {} // UnsafeWorkspaceServiceServer may be embedded to opt out of forward compatibility for this service. @@ -100,6 +143,27 @@ func _WorkspaceService_GetWorkspace_Handler(srv interface{}, ctx context.Context return interceptor(ctx, in, info, handler) } +func _WorkspaceService_WatchWorkspaceStatus_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(WatchWorkspaceStatusRequest) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(WorkspaceServiceServer).WatchWorkspaceStatus(m, &workspaceServiceWatchWorkspaceStatusServer{stream}) +} + +type WorkspaceService_WatchWorkspaceStatusServer interface { + Send(*WatchWorkspaceStatusResponse) error + grpc.ServerStream +} + +type workspaceServiceWatchWorkspaceStatusServer struct { + grpc.ServerStream +} + +func (x *workspaceServiceWatchWorkspaceStatusServer) Send(m *WatchWorkspaceStatusResponse) error { + return x.ServerStream.SendMsg(m) +} + // 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) @@ -112,6 +176,12 @@ var WorkspaceService_ServiceDesc = grpc.ServiceDesc{ Handler: _WorkspaceService_GetWorkspace_Handler, }, }, - Streams: []grpc.StreamDesc{}, + Streams: []grpc.StreamDesc{ + { + StreamName: "WatchWorkspaceStatus", + Handler: _WorkspaceService_WatchWorkspaceStatus_Handler, + ServerStreams: true, + }, + }, Metadata: "gitpod/v1/workspace.proto", } 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 a7083d3280b2f5..296b05b5ed158b 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 } from "./workspace_pb.js"; +import { GetWorkspaceRequest, GetWorkspaceResponse, WatchWorkspaceStatusRequest, WatchWorkspaceStatusResponse } from "./workspace_pb.js"; import { MethodKind } from "@bufbuild/protobuf"; /** @@ -32,5 +32,18 @@ export const WorkspaceService = { O: GetWorkspaceResponse, kind: MethodKind.Unary, }, + /** + * WatchWorkspaceStatus watchs the workspaces status changes + * + * ID +return NOT_FOUND Workspace does not exist + * + * @generated from rpc gitpod.v1.WorkspaceService.WatchWorkspaceStatus + */ + watchWorkspaceStatus: { + name: "WatchWorkspaceStatus", + I: WatchWorkspaceStatusRequest, + O: WatchWorkspaceStatusResponse, + kind: MethodKind.ServerStreaming, + }, } } 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 121c98b588b8c7..706575f59d5ac9 100644 --- a/components/public-api/typescript/src/gitpod/v1/workspace_pb.ts +++ b/components/public-api/typescript/src/gitpod/v1/workspace_pb.ts @@ -120,6 +120,86 @@ export class GetWorkspaceResponse extends Message { } } +/** + * @generated from message gitpod.v1.WatchWorkspaceStatusRequest + */ +export class WatchWorkspaceStatusRequest extends Message { + /** + * workspace_id specifies the workspace to watch + * + * +optional if empty then watch all workspaces + * + * @generated from field: optional string workspace_id = 1; + */ + workspaceId?: string; + + constructor(data?: PartialMessage) { + super(); + proto3.util.initPartial(data, this); + } + + static readonly runtime: typeof proto3 = proto3; + static readonly typeName = "gitpod.v1.WatchWorkspaceStatusRequest"; + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: "workspace_id", kind: "scalar", T: 9 /* ScalarType.STRING */, opt: true }, + ]); + + static fromBinary(bytes: Uint8Array, options?: Partial): WatchWorkspaceStatusRequest { + return new WatchWorkspaceStatusRequest().fromBinary(bytes, options); + } + + static fromJson(jsonValue: JsonValue, options?: Partial): WatchWorkspaceStatusRequest { + return new WatchWorkspaceStatusRequest().fromJson(jsonValue, options); + } + + static fromJsonString(jsonString: string, options?: Partial): WatchWorkspaceStatusRequest { + return new WatchWorkspaceStatusRequest().fromJsonString(jsonString, options); + } + + static equals(a: WatchWorkspaceStatusRequest | PlainMessage | undefined, b: WatchWorkspaceStatusRequest | PlainMessage | undefined): boolean { + return proto3.util.equals(WatchWorkspaceStatusRequest, a, b); + } +} + +/** + * @generated from message gitpod.v1.WatchWorkspaceStatusResponse + */ +export class WatchWorkspaceStatusResponse extends Message { + /** + * item is the workspace that was changed + * + * @generated from field: gitpod.v1.WorkspaceStatus status = 1; + */ + status?: WorkspaceStatus; + + constructor(data?: PartialMessage) { + super(); + proto3.util.initPartial(data, this); + } + + static readonly runtime: typeof proto3 = proto3; + static readonly typeName = "gitpod.v1.WatchWorkspaceStatusResponse"; + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: "status", kind: "message", T: WorkspaceStatus }, + ]); + + static fromBinary(bytes: Uint8Array, options?: Partial): WatchWorkspaceStatusResponse { + return new WatchWorkspaceStatusResponse().fromBinary(bytes, options); + } + + static fromJson(jsonValue: JsonValue, options?: Partial): WatchWorkspaceStatusResponse { + return new WatchWorkspaceStatusResponse().fromJson(jsonValue, options); + } + + static fromJsonString(jsonString: string, options?: Partial): WatchWorkspaceStatusResponse { + return new WatchWorkspaceStatusResponse().fromJsonString(jsonString, options); + } + + static equals(a: WatchWorkspaceStatusResponse | PlainMessage | undefined, b: WatchWorkspaceStatusResponse | PlainMessage | undefined): boolean { + return proto3.util.equals(WatchWorkspaceStatusResponse, a, b); + } +} + /** * +resource get workspace * diff --git a/components/server/package.json b/components/server/package.json index 9f56a3dbb40145..1226a6364fa48d 100644 --- a/components/server/package.json +++ b/components/server/package.json @@ -126,7 +126,7 @@ "@types/js-yaml": "^4.0.3", "@types/lodash.debounce": "^4.0.6", "@types/mocha": "^10.0.1", - "@types/node": "^16.11.0", + "@types/node": "^18.18.8", "@types/node-fetch": "^2.6.1", "@types/passport": "^1.0.7", "@types/passport-oauth2": "^1.4.11", diff --git a/components/server/src/api/workspace-service-api.ts b/components/server/src/api/workspace-service-api.ts index dbfed1576cdaf7..483c9ca1621981 100644 --- a/components/server/src/api/workspace-service-api.ts +++ b/components/server/src/api/workspace-service-api.ts @@ -25,4 +25,35 @@ export class WorkspaceServiceAPI implements ServiceImpl { + if (req.workspaceId) { + const instance = await this.workspaceService.getCurrentInstance(context.user.id, req.workspaceId); + const status = this.apiConverter.toWorkspace(instance).status; + if (status) { + const response = new WatchWorkspaceStatusResponse(); + response.status = status; + yield response; + } + } + const it = this.workspaceService.watchWorkspaceStatus(context.user.id, { signal: context.signal }); + for await (const instance of it) { + if (!instance) { + continue; + } + if (req.workspaceId && instance.workspaceId !== req.workspaceId) { + continue; + } + const status = this.apiConverter.toWorkspace(instance).status; + if (!status) { + continue; + } + const response = new WatchWorkspaceStatusResponse(); + response.status = status; + yield response; + } + } } diff --git a/components/server/src/workspace/workspace-service.ts b/components/server/src/workspace/workspace-service.ts index 237c483ee404e9..af8dc1f7d4497f 100644 --- a/components/server/src/workspace/workspace-service.ts +++ b/components/server/src/workspace/workspace-service.ts @@ -30,6 +30,7 @@ import { WorkspaceTimeoutDuration, } from "@gitpod/gitpod-protocol"; import { ErrorCodes, ApplicationError } from "@gitpod/gitpod-protocol/lib/messaging/error"; +import { generateAsyncGenerator } from "@gitpod/gitpod-protocol/lib/generate-async-generator"; import { Authorizer } from "../authorization/authorizer"; import { TraceContext } from "@gitpod/gitpod-protocol/lib/util/tracing"; import { WorkspaceFactory } from "./workspace-factory"; @@ -61,6 +62,7 @@ import { HeadlessLogEndpoint, HeadlessLogService } from "./headless-log-service" import { Deferred } from "@gitpod/gitpod-protocol/lib/util/deferred"; import { OrganizationService } from "../orgs/organization-service"; import { isGrpcError } from "@gitpod/gitpod-protocol/lib/util/grpc"; +import { RedisSubscriber } from "../messaging/redis-subscriber"; export interface StartWorkspaceOptions extends StarterStartWorkspaceOptions { /** @@ -85,6 +87,8 @@ export class WorkspaceService { @inject(RedisPublisher) private readonly publisher: RedisPublisher, @inject(HeadlessLogService) private readonly headlessLogService: HeadlessLogService, @inject(Authorizer) private readonly auth: Authorizer, + + @inject(RedisSubscriber) private readonly subscriber: RedisSubscriber, ) {} async createWorkspace( @@ -730,6 +734,15 @@ export class WorkspaceService { return urls; } + public watchWorkspaceStatus(userId: string, opts: { signal: AbortSignal }) { + return generateAsyncGenerator((sink) => { + const dispose = this.subscriber.listenForWorkspaceInstanceUpdates(userId, (_ctx, instance) => { + sink.next(instance); + }); + return dispose.dispose; + }, opts); + } + public async watchWorkspaceImageBuildLogs( userId: string, workspaceId: string, diff --git a/package.json b/package.json index 0b97d85be1a5a1..ed8eda81587b19 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "version": "0.0.0", "license": "UNLICENSED", "devDependencies": { - "@types/node": "^16.11.0", + "@types/node": "^18.18.8", "@types/shelljs": "^0.8.9", "json": "^11.0.0", "rimraf": "^3.0.2", diff --git a/yarn.lock b/yarn.lock index d0c08afa55e833..8cc6d63df23dbe 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3710,6 +3710,13 @@ resolved "https://registry.npmjs.org/@types/node/-/node-14.17.32.tgz" integrity sha512-JcII3D5/OapPGx+eJ+Ik1SQGyt6WvuqdRfh9jUwL6/iHGjmyOriBDciBUu7lEIBTL2ijxwrR70WUnw5AEDmFvQ== +"@types/node@^18.18.8": + version "18.18.8" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.18.8.tgz#2b285361f2357c8c8578ec86b5d097c7f464cfd6" + integrity sha512-OLGBaaK5V3VRBS1bAkMVP2/W9B+H8meUfl866OrMNQqt7wDgdpWPp5o6gmIc9pB+lIQHSq4ZL8ypeH1vPxcPaQ== + dependencies: + undici-types "~5.26.4" + "@types/oauth@*": version "0.9.1" resolved "https://registry.npmjs.org/@types/oauth/-/oauth-0.9.1.tgz" @@ -14980,6 +14987,11 @@ underscore@~1.13.2: resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.13.6.tgz#04786a1f589dc6c09f761fc5f45b89e935136441" integrity sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A== +undici-types@~5.26.4: + version "5.26.5" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" + integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== + undici@^5.25.4: version "5.25.4" resolved "https://registry.yarnpkg.com/undici/-/undici-5.25.4.tgz#7d8ef81d94f84cd384986271e5e5599b6dff4296" From 22248e490ac44f0bdf991aba3f89449dfac97ddc Mon Sep 17 00:00:00 2001 From: Huiwen Date: Tue, 7 Nov 2023 06:36:16 +0000 Subject: [PATCH 02/11] update import --- .../dashboard/src/service/json-rpc-workspace-client.ts | 7 ++++++- components/server/src/api/workspace-service-api.ts | 7 ++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/components/dashboard/src/service/json-rpc-workspace-client.ts b/components/dashboard/src/service/json-rpc-workspace-client.ts index ecef7df798aa1d..7bf6f4116ba485 100644 --- a/components/dashboard/src/service/json-rpc-workspace-client.ts +++ b/components/dashboard/src/service/json-rpc-workspace-client.ts @@ -7,7 +7,12 @@ import { CallOptions, Code, ConnectError, PromiseClient } from "@connectrpc/connect"; import { PartialMessage } from "@bufbuild/protobuf"; import { WorkspaceService } from "@gitpod/public-api/lib/gitpod/v1/workspace_connect"; -import { GetWorkspaceRequest, GetWorkspaceResponse } from "@gitpod/public-api/lib/gitpod/v1/workspace_pb"; +import { + GetWorkspaceRequest, + GetWorkspaceResponse, + WatchWorkspaceStatusRequest, + WatchWorkspaceStatusResponse, +} from "@gitpod/public-api/lib/gitpod/v1/workspace_pb"; import { converter } from "./public-api"; import { getGitpodService } from "./service"; import { generateAsyncGenerator } from "@gitpod/gitpod-protocol/lib/generate-async-generator"; diff --git a/components/server/src/api/workspace-service-api.ts b/components/server/src/api/workspace-service-api.ts index 483c9ca1621981..a28eca55e1bc98 100644 --- a/components/server/src/api/workspace-service-api.ts +++ b/components/server/src/api/workspace-service-api.ts @@ -6,7 +6,12 @@ import { HandlerContext, ServiceImpl } from "@connectrpc/connect"; import { WorkspaceService as WorkspaceServiceInterface } from "@gitpod/public-api/lib/gitpod/v1/workspace_connect"; -import { GetWorkspaceRequest, GetWorkspaceResponse } from "@gitpod/public-api/lib/gitpod/v1/workspace_pb"; +import { + GetWorkspaceRequest, + GetWorkspaceResponse, + WatchWorkspaceStatusRequest, + WatchWorkspaceStatusResponse, +} from "@gitpod/public-api/lib/gitpod/v1/workspace_pb"; import { inject, injectable } from "inversify"; import { WorkspaceService } from "../workspace/workspace-service"; import { PublicAPIConverter } from "@gitpod/gitpod-protocol/lib/public-api-converter"; From e11560dc7446b3d34d348f208a560e81c8f810e1 Mon Sep 17 00:00:00 2001 From: Huiwen Date: Tue, 7 Nov 2023 08:53:52 +0000 Subject: [PATCH 03/11] 1 --- components/public-api/gitpod/v1/workspace.proto | 2 +- components/public-api/go/v1/v1connect/workspace.connect.go | 4 ++-- components/public-api/go/v1/workspace_grpc.pb.go | 4 ++-- .../public-api/typescript/src/gitpod/v1/workspace_connect.ts | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/components/public-api/gitpod/v1/workspace.proto b/components/public-api/gitpod/v1/workspace.proto index aec1089114f4e7..c8ac42595811c1 100644 --- a/components/public-api/gitpod/v1/workspace.proto +++ b/components/public-api/gitpod/v1/workspace.proto @@ -15,7 +15,7 @@ service WorkspaceService { // WatchWorkspaceStatus watchs the workspaces status changes // - // ID +return NOT_FOUND Workspace does not exist + // workspace_id +return NOT_FOUND Workspace does not exist rpc WatchWorkspaceStatus(WatchWorkspaceStatusRequest) returns (stream WatchWorkspaceStatusResponse) {} } diff --git a/components/public-api/go/v1/v1connect/workspace.connect.go b/components/public-api/go/v1/v1connect/workspace.connect.go index 41696bec989387..5a9689843c68eb 100644 --- a/components/public-api/go/v1/v1connect/workspace.connect.go +++ b/components/public-api/go/v1/v1connect/workspace.connect.go @@ -38,7 +38,7 @@ type WorkspaceServiceClient interface { GetWorkspace(context.Context, *connect_go.Request[v1.GetWorkspaceRequest]) (*connect_go.Response[v1.GetWorkspaceResponse], error) // WatchWorkspaceStatus watchs the workspaces status changes // - // ID +return NOT_FOUND Workspace does not exist + // workspace_id +return NOT_FOUND Workspace does not exist WatchWorkspaceStatus(context.Context, *connect_go.Request[v1.WatchWorkspaceStatusRequest]) (*connect_go.ServerStreamForClient[v1.WatchWorkspaceStatusResponse], error) } @@ -90,7 +90,7 @@ type WorkspaceServiceHandler interface { GetWorkspace(context.Context, *connect_go.Request[v1.GetWorkspaceRequest]) (*connect_go.Response[v1.GetWorkspaceResponse], error) // WatchWorkspaceStatus watchs the workspaces status changes // - // ID +return NOT_FOUND Workspace does not exist + // workspace_id +return NOT_FOUND Workspace does not exist WatchWorkspaceStatus(context.Context, *connect_go.Request[v1.WatchWorkspaceStatusRequest], *connect_go.ServerStream[v1.WatchWorkspaceStatusResponse]) error } diff --git a/components/public-api/go/v1/workspace_grpc.pb.go b/components/public-api/go/v1/workspace_grpc.pb.go index 777e3567814415..4c6e80e895ff97 100644 --- a/components/public-api/go/v1/workspace_grpc.pb.go +++ b/components/public-api/go/v1/workspace_grpc.pb.go @@ -33,7 +33,7 @@ type WorkspaceServiceClient interface { GetWorkspace(ctx context.Context, in *GetWorkspaceRequest, opts ...grpc.CallOption) (*GetWorkspaceResponse, error) // WatchWorkspaceStatus watchs the workspaces status changes // - // ID +return NOT_FOUND Workspace does not exist + // workspace_id +return NOT_FOUND Workspace does not exist WatchWorkspaceStatus(ctx context.Context, in *WatchWorkspaceStatusRequest, opts ...grpc.CallOption) (WorkspaceService_WatchWorkspaceStatusClient, error) } @@ -97,7 +97,7 @@ type WorkspaceServiceServer interface { GetWorkspace(context.Context, *GetWorkspaceRequest) (*GetWorkspaceResponse, error) // WatchWorkspaceStatus watchs the workspaces status changes // - // ID +return NOT_FOUND Workspace does not exist + // workspace_id +return NOT_FOUND Workspace does not exist WatchWorkspaceStatus(*WatchWorkspaceStatusRequest, WorkspaceService_WatchWorkspaceStatusServer) error mustEmbedUnimplementedWorkspaceServiceServer() } 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 296b05b5ed158b..ad5a53d23637ce 100644 --- a/components/public-api/typescript/src/gitpod/v1/workspace_connect.ts +++ b/components/public-api/typescript/src/gitpod/v1/workspace_connect.ts @@ -35,7 +35,7 @@ export const WorkspaceService = { /** * WatchWorkspaceStatus watchs the workspaces status changes * - * ID +return NOT_FOUND Workspace does not exist + * workspace_id +return NOT_FOUND Workspace does not exist * * @generated from rpc gitpod.v1.WorkspaceService.WatchWorkspaceStatus */ From 50e75272294617ed5d8d24d2c2eedb009754df0b Mon Sep 17 00:00:00 2001 From: Huiwen Date: Tue, 7 Nov 2023 08:54:31 +0000 Subject: [PATCH 04/11] debug commit --- .../dashboard/src/service/public-api.ts | 21 +++++++++++++++++++ .../server/src/workspace/workspace-service.ts | 5 ++++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/components/dashboard/src/service/public-api.ts b/components/dashboard/src/service/public-api.ts index aa7f3ac354b1ec..50670847e84e53 100644 --- a/components/dashboard/src/service/public-api.ts +++ b/components/dashboard/src/service/public-api.ts @@ -46,6 +46,27 @@ export const organizationClient = createServiceClient( "organization", ); +// @ts-ignore +window.$testWorkspaceClient = workspaceClient; + +// @ts-ignore +window.$testWatch = (workspaceId: string) => { + const abortController = new AbortController(); + const it = workspaceClient.watchWorkspaceStatus( + { workspaceId }, + { + signal: abortController.signal, + }, + ); + const startWatchWorkspace = async () => { + for await (const workspace of it) { + console.log("rcv", workspaceId, JSON.stringify(workspace)); + } + }; + startWatchWorkspace().then().catch(console.error); + return abortController.abort.bind(abortController); +}; + export async function listAllProjects(opts: { orgId: string }): Promise { let pagination = { page: 1, diff --git a/components/server/src/workspace/workspace-service.ts b/components/server/src/workspace/workspace-service.ts index af8dc1f7d4497f..5e88042152d10d 100644 --- a/components/server/src/workspace/workspace-service.ts +++ b/components/server/src/workspace/workspace-service.ts @@ -739,7 +739,10 @@ export class WorkspaceService { const dispose = this.subscriber.listenForWorkspaceInstanceUpdates(userId, (_ctx, instance) => { sink.next(instance); }); - return dispose.dispose; + return () => { + console.log("=============dispose"); + dispose.dispose(); + }; }, opts); } From 0ef6e8e695eeb9761cefc3e9ffb9b4c0714c18c2 Mon Sep 17 00:00:00 2001 From: Huiwen Date: Tue, 7 Nov 2023 19:43:07 +0000 Subject: [PATCH 05/11] Add workspace_id to proto --- .../src/service/json-rpc-workspace-client.ts | 1 + .../public-api/gitpod/v1/workspace.proto | 7 +- components/public-api/go/v1/workspace.pb.go | 361 +++++++++--------- .../typescript/src/gitpod/v1/workspace_pb.ts | 14 +- .../server/src/api/workspace-service-api.ts | 1 + 5 files changed, 204 insertions(+), 180 deletions(-) diff --git a/components/dashboard/src/service/json-rpc-workspace-client.ts b/components/dashboard/src/service/json-rpc-workspace-client.ts index 7bf6f4116ba485..5e22cdf0902473 100644 --- a/components/dashboard/src/service/json-rpc-workspace-client.ts +++ b/components/dashboard/src/service/json-rpc-workspace-client.ts @@ -68,6 +68,7 @@ export class JsonRpcWorkspaceClient implements PromiseClient { /** - * item is the workspace that was changed + * workspace_id is the ID of the workspace that has status updated * - * @generated from field: gitpod.v1.WorkspaceStatus status = 1; + * @generated from field: string workspace_id = 1; + */ + workspaceId = ""; + + /** + * status is the updated status of workspace + * + * @generated from field: gitpod.v1.WorkspaceStatus status = 2; */ status?: WorkspaceStatus; @@ -180,7 +187,8 @@ export class WatchWorkspaceStatusResponse extends Message [ - { no: 1, name: "status", kind: "message", T: WorkspaceStatus }, + { no: 1, name: "workspace_id", kind: "scalar", T: 9 /* ScalarType.STRING */ }, + { no: 2, name: "status", kind: "message", T: WorkspaceStatus }, ]); static fromBinary(bytes: Uint8Array, options?: Partial): WatchWorkspaceStatusResponse { diff --git a/components/server/src/api/workspace-service-api.ts b/components/server/src/api/workspace-service-api.ts index a28eca55e1bc98..5c1062b8e62ffb 100644 --- a/components/server/src/api/workspace-service-api.ts +++ b/components/server/src/api/workspace-service-api.ts @@ -57,6 +57,7 @@ export class WorkspaceServiceAPI implements ServiceImpl Date: Wed, 8 Nov 2023 12:53:33 +0000 Subject: [PATCH 06/11] Add unit test for async generator --- components/gitpod-protocol/package.json | 1 + .../src/generate-async-generator.spec.ts | 128 ++++++++++++++++++ .../src/generate-async-generator.ts | 66 +++------ yarn.lock | 12 +- 4 files changed, 160 insertions(+), 47 deletions(-) create mode 100644 components/gitpod-protocol/src/generate-async-generator.spec.ts diff --git a/components/gitpod-protocol/package.json b/components/gitpod-protocol/package.json index 9a0761e39b6174..0ea7db93b48362 100644 --- a/components/gitpod-protocol/package.json +++ b/components/gitpod-protocol/package.json @@ -66,6 +66,7 @@ "analytics-node": "^6.0.0", "configcat-node": "^8.0.0", "cookie": "^0.4.2", + "event-iterator": "^2.0.0", "express": "^4.17.3", "google-protobuf": "^3.19.1", "inversify": "^6.0.1", diff --git a/components/gitpod-protocol/src/generate-async-generator.spec.ts b/components/gitpod-protocol/src/generate-async-generator.spec.ts new file mode 100644 index 00000000000000..6541916c312915 --- /dev/null +++ b/components/gitpod-protocol/src/generate-async-generator.spec.ts @@ -0,0 +1,128 @@ +/** + * 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 { suite, test } from "@testdeck/mocha"; +import * as chai from "chai"; + +import { generateAsyncGenerator } from "./generate-async-generator"; +import { Disposable } from "./util/disposable"; + +const expect = chai.expect; + +function watchWith(times: number, listener: (value: number) => void): Disposable { + let i = 0; + const cancel = setInterval(() => { + if (i < times) { + listener(i++); + } + }, 100); + return { + dispose: () => { + console.log("clean"); + clearInterval(cancel); + }, + }; +} + +const error = new Error("Test error"); +function watchIterator( + resultRef: { isDisposed: boolean; result: number[] }, + option: { errorAfter?: number; times: number; abortAfterMs?: number; setupError?: boolean }, +) { + const abortController = new AbortController(); + setTimeout(() => { + abortController.abort(); + }, option.abortAfterMs ?? 600); + return generateAsyncGenerator( + (sink) => { + try { + if (option.setupError) { + throw error; + } + const dispose = watchWith(option.times, (v) => { + if (option.errorAfter && option.errorAfter === v) { + sink.fail(error); + return; + } + sink.push(v); + }); + return () => { + resultRef.isDisposed = true; + dispose.dispose(); + }; + } catch (e) { + sink.fail(e as any as Error); + } + }, + { signal: abortController.signal }, + ); +} + +@suite +class TestGenerateAsyncGenerator { + @test public async "happy path"() { + const ref: { isDisposed: boolean; result: number[] } = { isDisposed: false, result: [] }; + const it = watchIterator(ref, { times: 5 }); + try { + for await (const v of it) { + ref.result.push(v); + } + expect.fail("should throw error"); + } catch (e) { + expect(e.message).to.be.equal("Abort error"); + expect(ref.result.length).to.be.equal(5); + expect(ref.isDisposed).to.be.equal(true); + } + } + + @test public async "should be stopped after abort signal is triggered"() { + const ref: { isDisposed: boolean; result: number[] } = { isDisposed: false, result: [] }; + const it = watchIterator(ref, { times: 5, abortAfterMs: 120 }); + try { + for await (const v of it) { + ref.result.push(v); + } + expect.fail("should throw error"); + } catch (e) { + expect(e.message).to.be.equal("Abort error"); + expect(ref.result[0]).to.be.equal(0); + expect(ref.result.length).to.be.equal(1); + expect(ref.isDisposed).to.be.equal(true); + } + } + + @test public async "should throw error if setup throws"() { + const ref: { isDisposed: boolean; result: number[] } = { isDisposed: false, result: [] }; + const it = watchIterator(ref, { times: 5, setupError: true }); + try { + for await (const v of it) { + ref.result.push(v); + } + expect.fail("should throw error"); + } catch (e) { + expect(e).to.be.equal(error); + expect(ref.result.length).to.be.equal(0); + expect(ref.isDisposed).to.be.equal(false); + } + } + + @test public async "should propagate errors from sink.next"() { + const ref: { isDisposed: boolean; result: number[] } = { isDisposed: false, result: [] }; + const it = watchIterator(ref, { times: 5, errorAfter: 2 }); + try { + for await (const v of it) { + ref.result.push(v); + } + expect.fail("should throw error"); + } catch (e) { + expect(e).to.be.equal(error); + expect(ref.result.length).to.be.equal(2); + expect(ref.isDisposed).to.be.equal(true); + } + } +} + +module.exports = new TestGenerateAsyncGenerator(); // Only to circumvent no usage warning :-/ diff --git a/components/gitpod-protocol/src/generate-async-generator.ts b/components/gitpod-protocol/src/generate-async-generator.ts index 8c85697e3b472d..f927928fc1a6f7 100644 --- a/components/gitpod-protocol/src/generate-async-generator.ts +++ b/components/gitpod-protocol/src/generate-async-generator.ts @@ -4,58 +4,32 @@ * See License.AGPL.txt in the project root for license information. */ -type Sink = { - next: (value: T | void) => void; -}; +import { EventIterator } from "event-iterator"; +import { Queue } from "event-iterator/lib/event-iterator"; /** * Generates an asynchronous generator that yields values based on the provided setup function. * - * the setup function that takes a sink and returns a cleanup function. - * setup sink object has a `next` method that accepts a value to be pushed to the generator. + * the setup function that takes a queue and returns a cleanup function. + * `queue.next` method that accepts a value to be pushed to the generator. + * + * remember that setup callback MUST wrap with try catch and use `queue.fail` to propagate error + * + * Iterator will always at least end with throw an `Abort error` */ -export async function* generateAsyncGenerator( - setup: (sink: Sink) => () => void, +export function generateAsyncGenerator( + setup: (queue: Queue) => (() => void) | void, opts: { signal: AbortSignal }, -): AsyncGenerator { - const queue: T[] = []; - - let resolveNext: ((value: T | void) => void) | null = null; - - const sink: Sink = { - next: (value: T | void) => { - if (resolveNext) { - resolveNext(value); - resolveNext = null; - } else { - if (value) { - queue.push(value); - } +) { + return new EventIterator((queue) => { + opts.signal.addEventListener("abort", () => { + queue.fail(new Error("Abort error")); + }); + const dispose = setup(queue); + return () => { + if (dispose) { + dispose(); } - }, - }; - - let isStopped = false; - opts.signal.addEventListener("abort", () => { - isStopped = true; - sink.next(); + }; }); - - const cleanup = setup(sink); - - try { - while (!isStopped) { - if (queue.length) { - yield queue.shift(); - } else { - yield new Promise((resolve) => { - resolveNext = resolve; - }); - } - } - // ignore error since code in `try` scope will not throw an error - // unless caller use it.throw, then it will throw to itself - } finally { - cleanup(); - } } diff --git a/yarn.lock b/yarn.lock index 8cc6d63df23dbe..5a184080687882 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3695,7 +3695,7 @@ "@types/node" "*" form-data "^3.0.0" -"@types/node@*", "@types/node@>=12.12.47", "@types/node@>=13.7.0", "@types/node@^16.11.0", "@types/node@^16.11.6": +"@types/node@*", "@types/node@>=12.12.47", "@types/node@>=13.7.0", "@types/node@^16.11.0": version "16.11.6" resolved "https://registry.npmjs.org/@types/node/-/node-16.11.6.tgz" integrity sha512-ua7PgUoeQFjmWPcoo9khiPum3Pd60k4/2ZGXt18sm2Slk0W0xZTqt5Y0Ny1NyBiN1EVQ/+FaF9NcY4Qe6rwk5w== @@ -3710,6 +3710,11 @@ resolved "https://registry.npmjs.org/@types/node/-/node-14.17.32.tgz" integrity sha512-JcII3D5/OapPGx+eJ+Ik1SQGyt6WvuqdRfh9jUwL6/iHGjmyOriBDciBUu7lEIBTL2ijxwrR70WUnw5AEDmFvQ== +"@types/node@^16.11.6": + version "16.18.61" + resolved "https://registry.yarnpkg.com/@types/node/-/node-16.18.61.tgz#5ea47e3018348bf3bbbe646b396ba5e720310be1" + integrity sha512-k0N7BqGhJoJzdh6MuQg1V1ragJiXTh8VUBAZTWjJ9cUq23SG0F0xavOwZbhiP4J3y20xd6jxKx+xNUhkMAi76Q== + "@types/node@^18.18.8": version "18.18.8" resolved "https://registry.yarnpkg.com/@types/node/-/node-18.18.8.tgz#2b285361f2357c8c8578ec86b5d097c7f464cfd6" @@ -7425,6 +7430,11 @@ etag@~1.8.1: resolved "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz" integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= +event-iterator@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/event-iterator/-/event-iterator-2.0.0.tgz#10f06740cc1e9fd6bc575f334c2bc1ae9d2dbf62" + integrity sha512-KGft0ldl31BZVV//jj+IAIGCxkvvUkkON+ScH6zfoX+l+omX6001ggyRSpI0Io2Hlro0ThXotswCtfzS8UkIiQ== + event-target-shim@^5.0.0: version "5.0.1" resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" From 75b1576278d01dabd3326d12241af1c5ea6a989a Mon Sep 17 00:00:00 2001 From: Huiwen Date: Wed, 8 Nov 2023 12:55:47 +0000 Subject: [PATCH 07/11] Update usage of func --- .../src/service/json-rpc-workspace-client.ts | 20 ++++++++++++------- .../server/src/workspace/workspace-service.ts | 18 ++++++++++------- 2 files changed, 24 insertions(+), 14 deletions(-) diff --git a/components/dashboard/src/service/json-rpc-workspace-client.ts b/components/dashboard/src/service/json-rpc-workspace-client.ts index 5e22cdf0902473..d743d0d3b4dec3 100644 --- a/components/dashboard/src/service/json-rpc-workspace-client.ts +++ b/components/dashboard/src/service/json-rpc-workspace-client.ts @@ -38,13 +38,19 @@ export class JsonRpcWorkspaceClient implements PromiseClient( - (sink) => { - const dispose = getGitpodService().registerClient({ - onInstanceUpdate: (instance) => { - sink.next(instance); - }, - }); - return dispose.dispose; + (queue) => { + try { + const dispose = getGitpodService().registerClient({ + onInstanceUpdate: (instance) => { + queue.push(instance); + }, + }); + return () => { + dispose.dispose(); + }; + } catch (e) { + queue.fail(e); + } }, { signal: options.signal }, ); diff --git a/components/server/src/workspace/workspace-service.ts b/components/server/src/workspace/workspace-service.ts index 5e88042152d10d..b452558dbd498a 100644 --- a/components/server/src/workspace/workspace-service.ts +++ b/components/server/src/workspace/workspace-service.ts @@ -736,13 +736,17 @@ export class WorkspaceService { public watchWorkspaceStatus(userId: string, opts: { signal: AbortSignal }) { return generateAsyncGenerator((sink) => { - const dispose = this.subscriber.listenForWorkspaceInstanceUpdates(userId, (_ctx, instance) => { - sink.next(instance); - }); - return () => { - console.log("=============dispose"); - dispose.dispose(); - }; + try { + const dispose = this.subscriber.listenForWorkspaceInstanceUpdates(userId, (_ctx, instance) => { + sink.push(instance); + }); + return () => { + console.log("=============dispose"); + dispose.dispose(); + }; + } catch (e) { + sink.fail(e); + } }, opts); } From 8a448fb78b76916591bf393d555d38357f7b0146 Mon Sep 17 00:00:00 2001 From: Huiwen Date: Wed, 8 Nov 2023 13:14:30 +0000 Subject: [PATCH 08/11] update test cases --- .../src/generate-async-generator.spec.ts | 80 +++++++++++++++---- 1 file changed, 66 insertions(+), 14 deletions(-) diff --git a/components/gitpod-protocol/src/generate-async-generator.spec.ts b/components/gitpod-protocol/src/generate-async-generator.spec.ts index 6541916c312915..6b5f9ad1f9e2f9 100644 --- a/components/gitpod-protocol/src/generate-async-generator.spec.ts +++ b/components/gitpod-protocol/src/generate-async-generator.spec.ts @@ -21,36 +21,46 @@ function watchWith(times: number, listener: (value: number) => void): Disposable }, 100); return { dispose: () => { - console.log("clean"); clearInterval(cancel); }, }; } const error = new Error("Test error"); -function watchIterator( - resultRef: { isDisposed: boolean; result: number[] }, - option: { errorAfter?: number; times: number; abortAfterMs?: number; setupError?: boolean }, -) { +interface Ref { + isDisposed: boolean; + result: number[]; + watchStarted: boolean; +} + +interface Option { + errorAfter?: number; + times: number; + abortAfterMs?: number; + setupError?: boolean; +} + +function watchIterator(ref: Ref, opts: Option) { const abortController = new AbortController(); setTimeout(() => { abortController.abort(); - }, option.abortAfterMs ?? 600); + }, opts.abortAfterMs ?? 600); return generateAsyncGenerator( (sink) => { try { - if (option.setupError) { + if (opts.setupError) { throw error; } - const dispose = watchWith(option.times, (v) => { - if (option.errorAfter && option.errorAfter === v) { + ref.watchStarted = true; + const dispose = watchWith(opts.times, (v) => { + if (opts.errorAfter && opts.errorAfter === v) { sink.fail(error); return; } sink.push(v); }); return () => { - resultRef.isDisposed = true; + ref.isDisposed = true; dispose.dispose(); }; } catch (e) { @@ -64,7 +74,7 @@ function watchIterator( @suite class TestGenerateAsyncGenerator { @test public async "happy path"() { - const ref: { isDisposed: boolean; result: number[] } = { isDisposed: false, result: [] }; + const ref: Ref = { isDisposed: false, result: [], watchStarted: false }; const it = watchIterator(ref, { times: 5 }); try { for await (const v of it) { @@ -72,6 +82,9 @@ class TestGenerateAsyncGenerator { } expect.fail("should throw error"); } catch (e) { + if (ref.watchStarted) { + expect(ref.isDisposed).to.be.equal(true); + } expect(e.message).to.be.equal("Abort error"); expect(ref.result.length).to.be.equal(5); expect(ref.isDisposed).to.be.equal(true); @@ -79,7 +92,7 @@ class TestGenerateAsyncGenerator { } @test public async "should be stopped after abort signal is triggered"() { - const ref: { isDisposed: boolean; result: number[] } = { isDisposed: false, result: [] }; + const ref: Ref = { isDisposed: false, result: [], watchStarted: false }; const it = watchIterator(ref, { times: 5, abortAfterMs: 120 }); try { for await (const v of it) { @@ -87,6 +100,9 @@ class TestGenerateAsyncGenerator { } expect.fail("should throw error"); } catch (e) { + if (ref.watchStarted) { + expect(ref.isDisposed).to.be.equal(true); + } expect(e.message).to.be.equal("Abort error"); expect(ref.result[0]).to.be.equal(0); expect(ref.result.length).to.be.equal(1); @@ -95,7 +111,7 @@ class TestGenerateAsyncGenerator { } @test public async "should throw error if setup throws"() { - const ref: { isDisposed: boolean; result: number[] } = { isDisposed: false, result: [] }; + const ref: Ref = { isDisposed: false, result: [], watchStarted: false }; const it = watchIterator(ref, { times: 5, setupError: true }); try { for await (const v of it) { @@ -103,6 +119,9 @@ class TestGenerateAsyncGenerator { } expect.fail("should throw error"); } catch (e) { + if (ref.watchStarted) { + expect(ref.isDisposed).to.be.equal(true); + } expect(e).to.be.equal(error); expect(ref.result.length).to.be.equal(0); expect(ref.isDisposed).to.be.equal(false); @@ -110,7 +129,7 @@ class TestGenerateAsyncGenerator { } @test public async "should propagate errors from sink.next"() { - const ref: { isDisposed: boolean; result: number[] } = { isDisposed: false, result: [] }; + const ref: Ref = { isDisposed: false, result: [], watchStarted: false }; const it = watchIterator(ref, { times: 5, errorAfter: 2 }); try { for await (const v of it) { @@ -118,11 +137,44 @@ class TestGenerateAsyncGenerator { } expect.fail("should throw error"); } catch (e) { + if (ref.watchStarted) { + expect(ref.isDisposed).to.be.equal(true); + } expect(e).to.be.equal(error); expect(ref.result.length).to.be.equal(2); expect(ref.isDisposed).to.be.equal(true); } } + + @test public async "should not start iterator if pre throw error in an iterator"() { + const ref: Ref = { isDisposed: false, result: [], watchStarted: false }; + const it = this.mockWatchWorkspaceStatus(ref, { times: 5, errorAfter: 2 }); + try { + for await (const v of it) { + ref.result.push(v); + } + expect.fail("should throw error"); + } catch (e) { + expect(ref.watchStarted).to.be.equal(false); + if (ref.watchStarted) { + expect(ref.isDisposed).to.be.equal(true); + } + expect(e.message).to.be.equal("Should throw error"); + expect(ref.result.length).to.be.equal(0); + expect(ref.isDisposed).to.be.equal(false); + } + } + + async *mockWatchWorkspaceStatus(ref: Ref, option: Option): AsyncIterable { + const shouldThrow = true; + if (shouldThrow) { + throw new Error("Should throw error"); + } + const it = watchIterator(ref, option); + for await (const item of it) { + yield item; + } + } } module.exports = new TestGenerateAsyncGenerator(); // Only to circumvent no usage warning :-/ From ed013476fe68c6b5f1925fd8ba507b4b4f925a87 Mon Sep 17 00:00:00 2001 From: Huiwen Date: Wed, 8 Nov 2023 13:15:32 +0000 Subject: [PATCH 09/11] fix json rpc watch --- .../src/service/json-rpc-workspace-client.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/components/dashboard/src/service/json-rpc-workspace-client.ts b/components/dashboard/src/service/json-rpc-workspace-client.ts index d743d0d3b4dec3..42e3314334ee96 100644 --- a/components/dashboard/src/service/json-rpc-workspace-client.ts +++ b/components/dashboard/src/service/json-rpc-workspace-client.ts @@ -37,6 +37,14 @@ export class JsonRpcWorkspaceClient implements PromiseClient( (queue) => { try { @@ -54,14 +62,6 @@ export class JsonRpcWorkspaceClient implements PromiseClient Date: Wed, 8 Nov 2023 14:29:53 +0000 Subject: [PATCH 10/11] 1 --- .../gitpod-protocol/src/generate-async-generator.spec.ts | 5 +++++ components/server/src/workspace/workspace-service.ts | 7 ++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/components/gitpod-protocol/src/generate-async-generator.spec.ts b/components/gitpod-protocol/src/generate-async-generator.spec.ts index 6b5f9ad1f9e2f9..4ffc364fdc43e2 100644 --- a/components/gitpod-protocol/src/generate-async-generator.spec.ts +++ b/components/gitpod-protocol/src/generate-async-generator.spec.ts @@ -87,6 +87,7 @@ class TestGenerateAsyncGenerator { } expect(e.message).to.be.equal("Abort error"); expect(ref.result.length).to.be.equal(5); + ref.result.forEach((v, i) => expect(v).to.be.equal(i)); expect(ref.isDisposed).to.be.equal(true); } } @@ -106,6 +107,7 @@ class TestGenerateAsyncGenerator { expect(e.message).to.be.equal("Abort error"); expect(ref.result[0]).to.be.equal(0); expect(ref.result.length).to.be.equal(1); + ref.result.forEach((v, i) => expect(v).to.be.equal(i)); expect(ref.isDisposed).to.be.equal(true); } } @@ -124,6 +126,7 @@ class TestGenerateAsyncGenerator { } expect(e).to.be.equal(error); expect(ref.result.length).to.be.equal(0); + ref.result.forEach((v, i) => expect(v).to.be.equal(i)); expect(ref.isDisposed).to.be.equal(false); } } @@ -142,6 +145,7 @@ class TestGenerateAsyncGenerator { } expect(e).to.be.equal(error); expect(ref.result.length).to.be.equal(2); + ref.result.forEach((v, i) => expect(v).to.be.equal(i)); expect(ref.isDisposed).to.be.equal(true); } } @@ -161,6 +165,7 @@ class TestGenerateAsyncGenerator { } expect(e.message).to.be.equal("Should throw error"); expect(ref.result.length).to.be.equal(0); + ref.result.forEach((v, i) => expect(v).to.be.equal(i)); expect(ref.isDisposed).to.be.equal(false); } } diff --git a/components/server/src/workspace/workspace-service.ts b/components/server/src/workspace/workspace-service.ts index b452558dbd498a..ba7a2e3904da53 100644 --- a/components/server/src/workspace/workspace-service.ts +++ b/components/server/src/workspace/workspace-service.ts @@ -745,7 +745,12 @@ export class WorkspaceService { dispose.dispose(); }; } catch (e) { - sink.fail(e); + if (e instanceof Error) { + sink.fail(e); + return; + } else { + sink.fail(new Error(String(e) || "unknown")); + } } }, opts); } From 7e5c72fd9c752b815b04c3ea43b16ac7ee602226 Mon Sep 17 00:00:00 2001 From: Huiwen Date: Wed, 8 Nov 2023 15:04:32 +0000 Subject: [PATCH 11/11] remove test commit and fix missing field --- .../src/service/json-rpc-workspace-client.ts | 1 + .../dashboard/src/service/public-api.ts | 21 ------------------- .../server/src/api/workspace-service-api.ts | 1 + .../server/src/workspace/workspace-service.ts | 1 - 4 files changed, 2 insertions(+), 22 deletions(-) diff --git a/components/dashboard/src/service/json-rpc-workspace-client.ts b/components/dashboard/src/service/json-rpc-workspace-client.ts index 42e3314334ee96..e789769ff0feb9 100644 --- a/components/dashboard/src/service/json-rpc-workspace-client.ts +++ b/components/dashboard/src/service/json-rpc-workspace-client.ts @@ -41,6 +41,7 @@ export class JsonRpcWorkspaceClient implements PromiseClient { - const abortController = new AbortController(); - const it = workspaceClient.watchWorkspaceStatus( - { workspaceId }, - { - signal: abortController.signal, - }, - ); - const startWatchWorkspace = async () => { - for await (const workspace of it) { - console.log("rcv", workspaceId, JSON.stringify(workspace)); - } - }; - startWatchWorkspace().then().catch(console.error); - return abortController.abort.bind(abortController); -}; - export async function listAllProjects(opts: { orgId: string }): Promise { let pagination = { page: 1, diff --git a/components/server/src/api/workspace-service-api.ts b/components/server/src/api/workspace-service-api.ts index 5c1062b8e62ffb..df0cf114208a62 100644 --- a/components/server/src/api/workspace-service-api.ts +++ b/components/server/src/api/workspace-service-api.ts @@ -40,6 +40,7 @@ export class WorkspaceServiceAPI implements ServiceImpl { - console.log("=============dispose"); dispose.dispose(); }; } catch (e) {