diff --git a/.github/workflows/typos.toml b/.github/workflows/typos.toml index 1948b920b1..23043a35e1 100644 --- a/.github/workflows/typos.toml +++ b/.github/workflows/typos.toml @@ -30,4 +30,5 @@ extend-exclude = [ "**/*_test.go", "**/*.test.tsx", "**/pnpm-lock.yaml", + "tailnet/testdata/**", ] diff --git a/agent/proto/agent.pb.go b/agent/proto/agent.pb.go index fb75710f1c..1042dd0314 100644 --- a/agent/proto/agent.pb.go +++ b/agent/proto/agent.pb.go @@ -231,7 +231,7 @@ func (x Stats_Metric_Type) Number() protoreflect.EnumNumber { // Deprecated: Use Stats_Metric_Type.Descriptor instead. func (Stats_Metric_Type) EnumDescriptor() ([]byte, []int) { - return file_agent_proto_agent_proto_rawDescGZIP(), []int{5, 1, 0} + return file_agent_proto_agent_proto_rawDescGZIP(), []int{7, 1, 0} } type Lifecycle_State int32 @@ -239,7 +239,7 @@ type Lifecycle_State int32 const ( Lifecycle_STATE_UNSPECIFIED Lifecycle_State = 0 Lifecycle_CREATED Lifecycle_State = 1 - Lifecycle_STARTED Lifecycle_State = 2 + Lifecycle_STARTING Lifecycle_State = 2 Lifecycle_START_TIMEOUT Lifecycle_State = 3 Lifecycle_START_ERROR Lifecycle_State = 4 Lifecycle_READY Lifecycle_State = 5 @@ -254,7 +254,7 @@ var ( Lifecycle_State_name = map[int32]string{ 0: "STATE_UNSPECIFIED", 1: "CREATED", - 2: "STARTED", + 2: "STARTING", 3: "START_TIMEOUT", 4: "START_ERROR", 5: "READY", @@ -266,7 +266,7 @@ var ( Lifecycle_State_value = map[string]int32{ "STATE_UNSPECIFIED": 0, "CREATED": 1, - "STARTED": 2, + "STARTING": 2, "START_TIMEOUT": 3, "START_ERROR": 4, "READY": 5, @@ -301,7 +301,59 @@ func (x Lifecycle_State) Number() protoreflect.EnumNumber { // Deprecated: Use Lifecycle_State.Descriptor instead. func (Lifecycle_State) EnumDescriptor() ([]byte, []int) { - return file_agent_proto_agent_proto_rawDescGZIP(), []int{8, 0} + return file_agent_proto_agent_proto_rawDescGZIP(), []int{10, 0} +} + +type Startup_Subsystem int32 + +const ( + Startup_SUBSYSTEM_UNSPECIFIED Startup_Subsystem = 0 + Startup_ENVBOX Startup_Subsystem = 1 + Startup_ENVBUILDER Startup_Subsystem = 2 + Startup_EXECTRACE Startup_Subsystem = 3 +) + +// Enum value maps for Startup_Subsystem. +var ( + Startup_Subsystem_name = map[int32]string{ + 0: "SUBSYSTEM_UNSPECIFIED", + 1: "ENVBOX", + 2: "ENVBUILDER", + 3: "EXECTRACE", + } + Startup_Subsystem_value = map[string]int32{ + "SUBSYSTEM_UNSPECIFIED": 0, + "ENVBOX": 1, + "ENVBUILDER": 2, + "EXECTRACE": 3, + } +) + +func (x Startup_Subsystem) Enum() *Startup_Subsystem { + p := new(Startup_Subsystem) + *p = x + return p +} + +func (x Startup_Subsystem) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (Startup_Subsystem) Descriptor() protoreflect.EnumDescriptor { + return file_agent_proto_agent_proto_enumTypes[5].Descriptor() +} + +func (Startup_Subsystem) Type() protoreflect.EnumType { + return &file_agent_proto_agent_proto_enumTypes[5] +} + +func (x Startup_Subsystem) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use Startup_Subsystem.Descriptor instead. +func (Startup_Subsystem) EnumDescriptor() ([]byte, []int) { + return file_agent_proto_agent_proto_rawDescGZIP(), []int{14, 0} } type Log_Level int32 @@ -346,11 +398,11 @@ func (x Log_Level) String() string { } func (Log_Level) Descriptor() protoreflect.EnumDescriptor { - return file_agent_proto_agent_proto_enumTypes[5].Descriptor() + return file_agent_proto_agent_proto_enumTypes[6].Descriptor() } func (Log_Level) Type() protoreflect.EnumType { - return &file_agent_proto_agent_proto_enumTypes[5] + return &file_agent_proto_agent_proto_enumTypes[6] } func (x Log_Level) Number() protoreflect.EnumNumber { @@ -359,7 +411,7 @@ func (x Log_Level) Number() protoreflect.EnumNumber { // Deprecated: Use Log_Level.Descriptor instead. func (Log_Level) EnumDescriptor() ([]byte, []int) { - return file_agent_proto_agent_proto_rawDescGZIP(), []int{17, 0} + return file_agent_proto_agent_proto_rawDescGZIP(), []int{19, 0} } type WorkspaceApp struct { @@ -367,7 +419,7 @@ type WorkspaceApp struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Uuid []byte `protobuf:"bytes,1,opt,name=uuid,proto3" json:"uuid,omitempty"` + Id []byte `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` Url string `protobuf:"bytes,2,opt,name=url,proto3" json:"url,omitempty"` External bool `protobuf:"varint,3,opt,name=external,proto3" json:"external,omitempty"` Slug string `protobuf:"bytes,4,opt,name=slug,proto3" json:"slug,omitempty"` @@ -377,7 +429,7 @@ type WorkspaceApp struct { Subdomain bool `protobuf:"varint,8,opt,name=subdomain,proto3" json:"subdomain,omitempty"` SubdomainName string `protobuf:"bytes,9,opt,name=subdomain_name,json=subdomainName,proto3" json:"subdomain_name,omitempty"` SharingLevel WorkspaceApp_SharingLevel `protobuf:"varint,10,opt,name=sharing_level,json=sharingLevel,proto3,enum=coder.agent.v2.WorkspaceApp_SharingLevel" json:"sharing_level,omitempty"` - Healthcheck *WorkspaceApp_HealthCheck `protobuf:"bytes,11,opt,name=healthcheck,proto3" json:"healthcheck,omitempty"` + Healthcheck *WorkspaceApp_Healthcheck `protobuf:"bytes,11,opt,name=healthcheck,proto3" json:"healthcheck,omitempty"` Health WorkspaceApp_Health `protobuf:"varint,12,opt,name=health,proto3,enum=coder.agent.v2.WorkspaceApp_Health" json:"health,omitempty"` } @@ -413,9 +465,9 @@ func (*WorkspaceApp) Descriptor() ([]byte, []int) { return file_agent_proto_agent_proto_rawDescGZIP(), []int{0} } -func (x *WorkspaceApp) GetUuid() []byte { +func (x *WorkspaceApp) GetId() []byte { if x != nil { - return x.Uuid + return x.Id } return nil } @@ -483,7 +535,7 @@ func (x *WorkspaceApp) GetSharingLevel() WorkspaceApp_SharingLevel { return WorkspaceApp_SHARING_LEVEL_UNSPECIFIED } -func (x *WorkspaceApp) GetHealthcheck() *WorkspaceApp_HealthCheck { +func (x *WorkspaceApp) GetHealthcheck() *WorkspaceApp_Healthcheck { if x != nil { return x.Healthcheck } @@ -497,21 +549,189 @@ func (x *WorkspaceApp) GetHealth() WorkspaceApp_Health { return WorkspaceApp_HEALTH_UNSPECIFIED } +type WorkspaceAgentScript struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + LogSourceId []byte `protobuf:"bytes,1,opt,name=log_source_id,json=logSourceId,proto3" json:"log_source_id,omitempty"` + LogPath string `protobuf:"bytes,2,opt,name=log_path,json=logPath,proto3" json:"log_path,omitempty"` + Script string `protobuf:"bytes,3,opt,name=script,proto3" json:"script,omitempty"` + Cron string `protobuf:"bytes,4,opt,name=cron,proto3" json:"cron,omitempty"` + RunOnStart bool `protobuf:"varint,5,opt,name=run_on_start,json=runOnStart,proto3" json:"run_on_start,omitempty"` + RunOnStop bool `protobuf:"varint,6,opt,name=run_on_stop,json=runOnStop,proto3" json:"run_on_stop,omitempty"` + StartBlocksLogin bool `protobuf:"varint,7,opt,name=start_blocks_login,json=startBlocksLogin,proto3" json:"start_blocks_login,omitempty"` + Timeout *durationpb.Duration `protobuf:"bytes,8,opt,name=timeout,proto3" json:"timeout,omitempty"` +} + +func (x *WorkspaceAgentScript) Reset() { + *x = WorkspaceAgentScript{} + if protoimpl.UnsafeEnabled { + mi := &file_agent_proto_agent_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *WorkspaceAgentScript) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*WorkspaceAgentScript) ProtoMessage() {} + +func (x *WorkspaceAgentScript) ProtoReflect() protoreflect.Message { + mi := &file_agent_proto_agent_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use WorkspaceAgentScript.ProtoReflect.Descriptor instead. +func (*WorkspaceAgentScript) Descriptor() ([]byte, []int) { + return file_agent_proto_agent_proto_rawDescGZIP(), []int{1} +} + +func (x *WorkspaceAgentScript) GetLogSourceId() []byte { + if x != nil { + return x.LogSourceId + } + return nil +} + +func (x *WorkspaceAgentScript) GetLogPath() string { + if x != nil { + return x.LogPath + } + return "" +} + +func (x *WorkspaceAgentScript) GetScript() string { + if x != nil { + return x.Script + } + return "" +} + +func (x *WorkspaceAgentScript) GetCron() string { + if x != nil { + return x.Cron + } + return "" +} + +func (x *WorkspaceAgentScript) GetRunOnStart() bool { + if x != nil { + return x.RunOnStart + } + return false +} + +func (x *WorkspaceAgentScript) GetRunOnStop() bool { + if x != nil { + return x.RunOnStop + } + return false +} + +func (x *WorkspaceAgentScript) GetStartBlocksLogin() bool { + if x != nil { + return x.StartBlocksLogin + } + return false +} + +func (x *WorkspaceAgentScript) GetTimeout() *durationpb.Duration { + if x != nil { + return x.Timeout + } + return nil +} + +type WorkspaceAgentMetadata struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Result *WorkspaceAgentMetadata_Result `protobuf:"bytes,1,opt,name=result,proto3" json:"result,omitempty"` + Description *WorkspaceAgentMetadata_Description `protobuf:"bytes,2,opt,name=description,proto3" json:"description,omitempty"` +} + +func (x *WorkspaceAgentMetadata) Reset() { + *x = WorkspaceAgentMetadata{} + if protoimpl.UnsafeEnabled { + mi := &file_agent_proto_agent_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *WorkspaceAgentMetadata) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*WorkspaceAgentMetadata) ProtoMessage() {} + +func (x *WorkspaceAgentMetadata) ProtoReflect() protoreflect.Message { + mi := &file_agent_proto_agent_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 WorkspaceAgentMetadata.ProtoReflect.Descriptor instead. +func (*WorkspaceAgentMetadata) Descriptor() ([]byte, []int) { + return file_agent_proto_agent_proto_rawDescGZIP(), []int{2} +} + +func (x *WorkspaceAgentMetadata) GetResult() *WorkspaceAgentMetadata_Result { + if x != nil { + return x.Result + } + return nil +} + +func (x *WorkspaceAgentMetadata) GetDescription() *WorkspaceAgentMetadata_Description { + if x != nil { + return x.Description + } + return nil +} + type Manifest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - GitAuthConfigs uint32 `protobuf:"varint,1,opt,name=git_auth_configs,json=gitAuthConfigs,proto3" json:"git_auth_configs,omitempty"` - VsCodePortProxyUri string `protobuf:"bytes,2,opt,name=vs_code_port_proxy_uri,json=vsCodePortProxyUri,proto3" json:"vs_code_port_proxy_uri,omitempty"` - Apps []*WorkspaceApp `protobuf:"bytes,3,rep,name=apps,proto3" json:"apps,omitempty"` - DerpMap *proto.DERPMap `protobuf:"bytes,4,opt,name=derp_map,json=derpMap,proto3" json:"derp_map,omitempty"` + AgentId []byte `protobuf:"bytes,1,opt,name=agent_id,json=agentId,proto3" json:"agent_id,omitempty"` + OwnerUsername string `protobuf:"bytes,13,opt,name=owner_username,json=ownerUsername,proto3" json:"owner_username,omitempty"` + WorkspaceId []byte `protobuf:"bytes,14,opt,name=workspace_id,json=workspaceId,proto3" json:"workspace_id,omitempty"` + GitAuthConfigs uint32 `protobuf:"varint,2,opt,name=git_auth_configs,json=gitAuthConfigs,proto3" json:"git_auth_configs,omitempty"` + EnvironmentVariables map[string]string `protobuf:"bytes,3,rep,name=environment_variables,json=environmentVariables,proto3" json:"environment_variables,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + Directory string `protobuf:"bytes,4,opt,name=directory,proto3" json:"directory,omitempty"` + VsCodePortProxyUri string `protobuf:"bytes,5,opt,name=vs_code_port_proxy_uri,json=vsCodePortProxyUri,proto3" json:"vs_code_port_proxy_uri,omitempty"` + MotdPath string `protobuf:"bytes,6,opt,name=motd_path,json=motdPath,proto3" json:"motd_path,omitempty"` + DisableDirectConnections bool `protobuf:"varint,7,opt,name=disable_direct_connections,json=disableDirectConnections,proto3" json:"disable_direct_connections,omitempty"` + DerpForceWebsockets bool `protobuf:"varint,8,opt,name=derp_force_websockets,json=derpForceWebsockets,proto3" json:"derp_force_websockets,omitempty"` + DerpMap *proto.DERPMap `protobuf:"bytes,9,opt,name=derp_map,json=derpMap,proto3" json:"derp_map,omitempty"` + Scripts []*WorkspaceAgentScript `protobuf:"bytes,10,rep,name=scripts,proto3" json:"scripts,omitempty"` + Apps []*WorkspaceApp `protobuf:"bytes,11,rep,name=apps,proto3" json:"apps,omitempty"` + Metadata []*WorkspaceAgentMetadata_Description `protobuf:"bytes,12,rep,name=metadata,proto3" json:"metadata,omitempty"` } func (x *Manifest) Reset() { *x = Manifest{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[1] + mi := &file_agent_proto_agent_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -524,7 +744,7 @@ func (x *Manifest) String() string { func (*Manifest) ProtoMessage() {} func (x *Manifest) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[1] + mi := &file_agent_proto_agent_proto_msgTypes[3] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -537,7 +757,28 @@ func (x *Manifest) ProtoReflect() protoreflect.Message { // Deprecated: Use Manifest.ProtoReflect.Descriptor instead. func (*Manifest) Descriptor() ([]byte, []int) { - return file_agent_proto_agent_proto_rawDescGZIP(), []int{1} + return file_agent_proto_agent_proto_rawDescGZIP(), []int{3} +} + +func (x *Manifest) GetAgentId() []byte { + if x != nil { + return x.AgentId + } + return nil +} + +func (x *Manifest) GetOwnerUsername() string { + if x != nil { + return x.OwnerUsername + } + return "" +} + +func (x *Manifest) GetWorkspaceId() []byte { + if x != nil { + return x.WorkspaceId + } + return nil } func (x *Manifest) GetGitAuthConfigs() uint32 { @@ -547,6 +788,20 @@ func (x *Manifest) GetGitAuthConfigs() uint32 { return 0 } +func (x *Manifest) GetEnvironmentVariables() map[string]string { + if x != nil { + return x.EnvironmentVariables + } + return nil +} + +func (x *Manifest) GetDirectory() string { + if x != nil { + return x.Directory + } + return "" +} + func (x *Manifest) GetVsCodePortProxyUri() string { if x != nil { return x.VsCodePortProxyUri @@ -554,6 +809,41 @@ func (x *Manifest) GetVsCodePortProxyUri() string { return "" } +func (x *Manifest) GetMotdPath() string { + if x != nil { + return x.MotdPath + } + return "" +} + +func (x *Manifest) GetDisableDirectConnections() bool { + if x != nil { + return x.DisableDirectConnections + } + return false +} + +func (x *Manifest) GetDerpForceWebsockets() bool { + if x != nil { + return x.DerpForceWebsockets + } + return false +} + +func (x *Manifest) GetDerpMap() *proto.DERPMap { + if x != nil { + return x.DerpMap + } + return nil +} + +func (x *Manifest) GetScripts() []*WorkspaceAgentScript { + if x != nil { + return x.Scripts + } + return nil +} + func (x *Manifest) GetApps() []*WorkspaceApp { if x != nil { return x.Apps @@ -561,9 +851,9 @@ func (x *Manifest) GetApps() []*WorkspaceApp { return nil } -func (x *Manifest) GetDerpMap() *proto.DERPMap { +func (x *Manifest) GetMetadata() []*WorkspaceAgentMetadata_Description { if x != nil { - return x.DerpMap + return x.Metadata } return nil } @@ -577,7 +867,7 @@ type GetManifestRequest struct { func (x *GetManifestRequest) Reset() { *x = GetManifestRequest{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[2] + mi := &file_agent_proto_agent_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -590,7 +880,7 @@ func (x *GetManifestRequest) String() string { func (*GetManifestRequest) ProtoMessage() {} func (x *GetManifestRequest) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[2] + mi := &file_agent_proto_agent_proto_msgTypes[4] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -603,7 +893,7 @@ func (x *GetManifestRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetManifestRequest.ProtoReflect.Descriptor instead. func (*GetManifestRequest) Descriptor() ([]byte, []int) { - return file_agent_proto_agent_proto_rawDescGZIP(), []int{2} + return file_agent_proto_agent_proto_rawDescGZIP(), []int{4} } type ServiceBanner struct { @@ -619,7 +909,7 @@ type ServiceBanner struct { func (x *ServiceBanner) Reset() { *x = ServiceBanner{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[3] + mi := &file_agent_proto_agent_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -632,7 +922,7 @@ func (x *ServiceBanner) String() string { func (*ServiceBanner) ProtoMessage() {} func (x *ServiceBanner) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[3] + mi := &file_agent_proto_agent_proto_msgTypes[5] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -645,7 +935,7 @@ func (x *ServiceBanner) ProtoReflect() protoreflect.Message { // Deprecated: Use ServiceBanner.ProtoReflect.Descriptor instead. func (*ServiceBanner) Descriptor() ([]byte, []int) { - return file_agent_proto_agent_proto_rawDescGZIP(), []int{3} + return file_agent_proto_agent_proto_rawDescGZIP(), []int{5} } func (x *ServiceBanner) GetEnabled() bool { @@ -678,7 +968,7 @@ type GetServiceBannerRequest struct { func (x *GetServiceBannerRequest) Reset() { *x = GetServiceBannerRequest{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[4] + mi := &file_agent_proto_agent_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -691,7 +981,7 @@ func (x *GetServiceBannerRequest) String() string { func (*GetServiceBannerRequest) ProtoMessage() {} func (x *GetServiceBannerRequest) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[4] + mi := &file_agent_proto_agent_proto_msgTypes[6] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -704,7 +994,7 @@ func (x *GetServiceBannerRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetServiceBannerRequest.ProtoReflect.Descriptor instead. func (*GetServiceBannerRequest) Descriptor() ([]byte, []int) { - return file_agent_proto_agent_proto_rawDescGZIP(), []int{4} + return file_agent_proto_agent_proto_rawDescGZIP(), []int{6} } type Stats struct { @@ -737,13 +1027,14 @@ type Stats struct { SessionCountReconnectingPty int64 `protobuf:"varint,10,opt,name=session_count_reconnecting_pty,json=sessionCountReconnectingPty,proto3" json:"session_count_reconnecting_pty,omitempty"` // SessionCountSSH is the number of connections received by an agent // that are normal, non-tagged SSH sessions. - SessionCountSsh int64 `protobuf:"varint,11,opt,name=session_count_ssh,json=sessionCountSsh,proto3" json:"session_count_ssh,omitempty"` + SessionCountSsh int64 `protobuf:"varint,11,opt,name=session_count_ssh,json=sessionCountSsh,proto3" json:"session_count_ssh,omitempty"` + Metrics []*Stats_Metric `protobuf:"bytes,12,rep,name=metrics,proto3" json:"metrics,omitempty"` } func (x *Stats) Reset() { *x = Stats{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[5] + mi := &file_agent_proto_agent_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -756,7 +1047,7 @@ func (x *Stats) String() string { func (*Stats) ProtoMessage() {} func (x *Stats) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[5] + mi := &file_agent_proto_agent_proto_msgTypes[7] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -769,7 +1060,7 @@ func (x *Stats) ProtoReflect() protoreflect.Message { // Deprecated: Use Stats.ProtoReflect.Descriptor instead. func (*Stats) Descriptor() ([]byte, []int) { - return file_agent_proto_agent_proto_rawDescGZIP(), []int{5} + return file_agent_proto_agent_proto_rawDescGZIP(), []int{7} } func (x *Stats) GetConnectionsByProto() map[string]int64 { @@ -849,6 +1140,13 @@ func (x *Stats) GetSessionCountSsh() int64 { return 0 } +func (x *Stats) GetMetrics() []*Stats_Metric { + if x != nil { + return x.Metrics + } + return nil +} + type UpdateStatsRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -860,7 +1158,7 @@ type UpdateStatsRequest struct { func (x *UpdateStatsRequest) Reset() { *x = UpdateStatsRequest{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[6] + mi := &file_agent_proto_agent_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -873,7 +1171,7 @@ func (x *UpdateStatsRequest) String() string { func (*UpdateStatsRequest) ProtoMessage() {} func (x *UpdateStatsRequest) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[6] + mi := &file_agent_proto_agent_proto_msgTypes[8] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -886,7 +1184,7 @@ func (x *UpdateStatsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateStatsRequest.ProtoReflect.Descriptor instead. func (*UpdateStatsRequest) Descriptor() ([]byte, []int) { - return file_agent_proto_agent_proto_rawDescGZIP(), []int{6} + return file_agent_proto_agent_proto_rawDescGZIP(), []int{8} } func (x *UpdateStatsRequest) GetStats() *Stats { @@ -901,13 +1199,13 @@ type UpdateStatsResponse struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - ReportIntervalNanoseconds *durationpb.Duration `protobuf:"bytes,1,opt,name=report_interval_nanoseconds,json=reportIntervalNanoseconds,proto3" json:"report_interval_nanoseconds,omitempty"` + ReportInterval *durationpb.Duration `protobuf:"bytes,1,opt,name=report_interval,json=reportInterval,proto3" json:"report_interval,omitempty"` } func (x *UpdateStatsResponse) Reset() { *x = UpdateStatsResponse{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[7] + mi := &file_agent_proto_agent_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -920,7 +1218,7 @@ func (x *UpdateStatsResponse) String() string { func (*UpdateStatsResponse) ProtoMessage() {} func (x *UpdateStatsResponse) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[7] + mi := &file_agent_proto_agent_proto_msgTypes[9] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -933,12 +1231,12 @@ func (x *UpdateStatsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateStatsResponse.ProtoReflect.Descriptor instead. func (*UpdateStatsResponse) Descriptor() ([]byte, []int) { - return file_agent_proto_agent_proto_rawDescGZIP(), []int{7} + return file_agent_proto_agent_proto_rawDescGZIP(), []int{9} } -func (x *UpdateStatsResponse) GetReportIntervalNanoseconds() *durationpb.Duration { +func (x *UpdateStatsResponse) GetReportInterval() *durationpb.Duration { if x != nil { - return x.ReportIntervalNanoseconds + return x.ReportInterval } return nil } @@ -948,13 +1246,14 @@ type Lifecycle struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - State Lifecycle_State `protobuf:"varint,1,opt,name=state,proto3,enum=coder.agent.v2.Lifecycle_State" json:"state,omitempty"` + State Lifecycle_State `protobuf:"varint,1,opt,name=state,proto3,enum=coder.agent.v2.Lifecycle_State" json:"state,omitempty"` + ChangedAt *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=changed_at,json=changedAt,proto3" json:"changed_at,omitempty"` } func (x *Lifecycle) Reset() { *x = Lifecycle{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[8] + mi := &file_agent_proto_agent_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -967,7 +1266,7 @@ func (x *Lifecycle) String() string { func (*Lifecycle) ProtoMessage() {} func (x *Lifecycle) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[8] + mi := &file_agent_proto_agent_proto_msgTypes[10] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -980,7 +1279,7 @@ func (x *Lifecycle) ProtoReflect() protoreflect.Message { // Deprecated: Use Lifecycle.ProtoReflect.Descriptor instead. func (*Lifecycle) Descriptor() ([]byte, []int) { - return file_agent_proto_agent_proto_rawDescGZIP(), []int{8} + return file_agent_proto_agent_proto_rawDescGZIP(), []int{10} } func (x *Lifecycle) GetState() Lifecycle_State { @@ -990,6 +1289,13 @@ func (x *Lifecycle) GetState() Lifecycle_State { return Lifecycle_STATE_UNSPECIFIED } +func (x *Lifecycle) GetChangedAt() *timestamppb.Timestamp { + if x != nil { + return x.ChangedAt + } + return nil +} + type UpdateLifecycleRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -1001,7 +1307,7 @@ type UpdateLifecycleRequest struct { func (x *UpdateLifecycleRequest) Reset() { *x = UpdateLifecycleRequest{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[9] + mi := &file_agent_proto_agent_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1014,7 +1320,7 @@ func (x *UpdateLifecycleRequest) String() string { func (*UpdateLifecycleRequest) ProtoMessage() {} func (x *UpdateLifecycleRequest) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[9] + mi := &file_agent_proto_agent_proto_msgTypes[11] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1027,7 +1333,7 @@ func (x *UpdateLifecycleRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateLifecycleRequest.ProtoReflect.Descriptor instead. func (*UpdateLifecycleRequest) Descriptor() ([]byte, []int) { - return file_agent_proto_agent_proto_rawDescGZIP(), []int{9} + return file_agent_proto_agent_proto_rawDescGZIP(), []int{11} } func (x *UpdateLifecycleRequest) GetLifecycle() *Lifecycle { @@ -1048,7 +1354,7 @@ type BatchUpdateAppHealthRequest struct { func (x *BatchUpdateAppHealthRequest) Reset() { *x = BatchUpdateAppHealthRequest{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[10] + mi := &file_agent_proto_agent_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1061,7 +1367,7 @@ func (x *BatchUpdateAppHealthRequest) String() string { func (*BatchUpdateAppHealthRequest) ProtoMessage() {} func (x *BatchUpdateAppHealthRequest) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[10] + mi := &file_agent_proto_agent_proto_msgTypes[12] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1074,7 +1380,7 @@ func (x *BatchUpdateAppHealthRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use BatchUpdateAppHealthRequest.ProtoReflect.Descriptor instead. func (*BatchUpdateAppHealthRequest) Descriptor() ([]byte, []int) { - return file_agent_proto_agent_proto_rawDescGZIP(), []int{10} + return file_agent_proto_agent_proto_rawDescGZIP(), []int{12} } func (x *BatchUpdateAppHealthRequest) GetUpdates() []*BatchUpdateAppHealthRequest_HealthUpdate { @@ -1093,7 +1399,7 @@ type BatchUpdateAppHealthResponse struct { func (x *BatchUpdateAppHealthResponse) Reset() { *x = BatchUpdateAppHealthResponse{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[11] + mi := &file_agent_proto_agent_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1106,7 +1412,7 @@ func (x *BatchUpdateAppHealthResponse) String() string { func (*BatchUpdateAppHealthResponse) ProtoMessage() {} func (x *BatchUpdateAppHealthResponse) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[11] + mi := &file_agent_proto_agent_proto_msgTypes[13] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1119,7 +1425,7 @@ func (x *BatchUpdateAppHealthResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use BatchUpdateAppHealthResponse.ProtoReflect.Descriptor instead. func (*BatchUpdateAppHealthResponse) Descriptor() ([]byte, []int) { - return file_agent_proto_agent_proto_rawDescGZIP(), []int{11} + return file_agent_proto_agent_proto_rawDescGZIP(), []int{13} } type Startup struct { @@ -1127,15 +1433,15 @@ type Startup struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Version string `protobuf:"bytes,1,opt,name=version,proto3" json:"version,omitempty"` - ExpandedDirectory string `protobuf:"bytes,2,opt,name=expanded_directory,json=expandedDirectory,proto3" json:"expanded_directory,omitempty"` - Subsystems []string `protobuf:"bytes,3,rep,name=subsystems,proto3" json:"subsystems,omitempty"` + Version string `protobuf:"bytes,1,opt,name=version,proto3" json:"version,omitempty"` + ExpandedDirectory string `protobuf:"bytes,2,opt,name=expanded_directory,json=expandedDirectory,proto3" json:"expanded_directory,omitempty"` + Subsystems []Startup_Subsystem `protobuf:"varint,3,rep,packed,name=subsystems,proto3,enum=coder.agent.v2.Startup_Subsystem" json:"subsystems,omitempty"` } func (x *Startup) Reset() { *x = Startup{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[12] + mi := &file_agent_proto_agent_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1148,7 +1454,7 @@ func (x *Startup) String() string { func (*Startup) ProtoMessage() {} func (x *Startup) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[12] + mi := &file_agent_proto_agent_proto_msgTypes[14] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1161,7 +1467,7 @@ func (x *Startup) ProtoReflect() protoreflect.Message { // Deprecated: Use Startup.ProtoReflect.Descriptor instead. func (*Startup) Descriptor() ([]byte, []int) { - return file_agent_proto_agent_proto_rawDescGZIP(), []int{12} + return file_agent_proto_agent_proto_rawDescGZIP(), []int{14} } func (x *Startup) GetVersion() string { @@ -1178,7 +1484,7 @@ func (x *Startup) GetExpandedDirectory() string { return "" } -func (x *Startup) GetSubsystems() []string { +func (x *Startup) GetSubsystems() []Startup_Subsystem { if x != nil { return x.Subsystems } @@ -1196,7 +1502,7 @@ type UpdateStartupRequest struct { func (x *UpdateStartupRequest) Reset() { *x = UpdateStartupRequest{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[13] + mi := &file_agent_proto_agent_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1209,7 +1515,7 @@ func (x *UpdateStartupRequest) String() string { func (*UpdateStartupRequest) ProtoMessage() {} func (x *UpdateStartupRequest) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[13] + mi := &file_agent_proto_agent_proto_msgTypes[15] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1222,7 +1528,7 @@ func (x *UpdateStartupRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateStartupRequest.ProtoReflect.Descriptor instead. func (*UpdateStartupRequest) Descriptor() ([]byte, []int) { - return file_agent_proto_agent_proto_rawDescGZIP(), []int{13} + return file_agent_proto_agent_proto_rawDescGZIP(), []int{15} } func (x *UpdateStartupRequest) GetStartup() *Startup { @@ -1237,17 +1543,14 @@ type Metadata struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` - CollectedAt *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=collected_at,json=collectedAt,proto3" json:"collected_at,omitempty"` - Age int64 `protobuf:"varint,3,opt,name=age,proto3" json:"age,omitempty"` - Value string `protobuf:"bytes,4,opt,name=value,proto3" json:"value,omitempty"` - Error string `protobuf:"bytes,5,opt,name=error,proto3" json:"error,omitempty"` + Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` + Result *WorkspaceAgentMetadata_Result `protobuf:"bytes,2,opt,name=result,proto3" json:"result,omitempty"` } func (x *Metadata) Reset() { *x = Metadata{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[14] + mi := &file_agent_proto_agent_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1260,7 +1563,7 @@ func (x *Metadata) String() string { func (*Metadata) ProtoMessage() {} func (x *Metadata) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[14] + mi := &file_agent_proto_agent_proto_msgTypes[16] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1273,7 +1576,7 @@ func (x *Metadata) ProtoReflect() protoreflect.Message { // Deprecated: Use Metadata.ProtoReflect.Descriptor instead. func (*Metadata) Descriptor() ([]byte, []int) { - return file_agent_proto_agent_proto_rawDescGZIP(), []int{14} + return file_agent_proto_agent_proto_rawDescGZIP(), []int{16} } func (x *Metadata) GetKey() string { @@ -1283,34 +1586,13 @@ func (x *Metadata) GetKey() string { return "" } -func (x *Metadata) GetCollectedAt() *timestamppb.Timestamp { +func (x *Metadata) GetResult() *WorkspaceAgentMetadata_Result { if x != nil { - return x.CollectedAt + return x.Result } return nil } -func (x *Metadata) GetAge() int64 { - if x != nil { - return x.Age - } - return 0 -} - -func (x *Metadata) GetValue() string { - if x != nil { - return x.Value - } - return "" -} - -func (x *Metadata) GetError() string { - if x != nil { - return x.Error - } - return "" -} - type BatchUpdateMetadataRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -1322,7 +1604,7 @@ type BatchUpdateMetadataRequest struct { func (x *BatchUpdateMetadataRequest) Reset() { *x = BatchUpdateMetadataRequest{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[15] + mi := &file_agent_proto_agent_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1335,7 +1617,7 @@ func (x *BatchUpdateMetadataRequest) String() string { func (*BatchUpdateMetadataRequest) ProtoMessage() {} func (x *BatchUpdateMetadataRequest) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[15] + mi := &file_agent_proto_agent_proto_msgTypes[17] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1348,7 +1630,7 @@ func (x *BatchUpdateMetadataRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use BatchUpdateMetadataRequest.ProtoReflect.Descriptor instead. func (*BatchUpdateMetadataRequest) Descriptor() ([]byte, []int) { - return file_agent_proto_agent_proto_rawDescGZIP(), []int{15} + return file_agent_proto_agent_proto_rawDescGZIP(), []int{17} } func (x *BatchUpdateMetadataRequest) GetMetadata() []*Metadata { @@ -1367,7 +1649,7 @@ type BatchUpdateMetadataResponse struct { func (x *BatchUpdateMetadataResponse) Reset() { *x = BatchUpdateMetadataResponse{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[16] + mi := &file_agent_proto_agent_proto_msgTypes[18] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1380,7 +1662,7 @@ func (x *BatchUpdateMetadataResponse) String() string { func (*BatchUpdateMetadataResponse) ProtoMessage() {} func (x *BatchUpdateMetadataResponse) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[16] + mi := &file_agent_proto_agent_proto_msgTypes[18] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1393,7 +1675,7 @@ func (x *BatchUpdateMetadataResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use BatchUpdateMetadataResponse.ProtoReflect.Descriptor instead. func (*BatchUpdateMetadataResponse) Descriptor() ([]byte, []int) { - return file_agent_proto_agent_proto_rawDescGZIP(), []int{16} + return file_agent_proto_agent_proto_rawDescGZIP(), []int{18} } type Log struct { @@ -1409,7 +1691,7 @@ type Log struct { func (x *Log) Reset() { *x = Log{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[17] + mi := &file_agent_proto_agent_proto_msgTypes[19] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1422,7 +1704,7 @@ func (x *Log) String() string { func (*Log) ProtoMessage() {} func (x *Log) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[17] + mi := &file_agent_proto_agent_proto_msgTypes[19] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1435,7 +1717,7 @@ func (x *Log) ProtoReflect() protoreflect.Message { // Deprecated: Use Log.ProtoReflect.Descriptor instead. func (*Log) Descriptor() ([]byte, []int) { - return file_agent_proto_agent_proto_rawDescGZIP(), []int{17} + return file_agent_proto_agent_proto_rawDescGZIP(), []int{19} } func (x *Log) GetCreatedAt() *timestamppb.Timestamp { @@ -1464,14 +1746,14 @@ type BatchCreateLogsRequest struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - SourceId []byte `protobuf:"bytes,1,opt,name=source_id,json=sourceId,proto3" json:"source_id,omitempty"` - Logs []*Log `protobuf:"bytes,2,rep,name=logs,proto3" json:"logs,omitempty"` + LogSourceId []byte `protobuf:"bytes,1,opt,name=log_source_id,json=logSourceId,proto3" json:"log_source_id,omitempty"` + Logs []*Log `protobuf:"bytes,2,rep,name=logs,proto3" json:"logs,omitempty"` } func (x *BatchCreateLogsRequest) Reset() { *x = BatchCreateLogsRequest{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[18] + mi := &file_agent_proto_agent_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1484,7 +1766,7 @@ func (x *BatchCreateLogsRequest) String() string { func (*BatchCreateLogsRequest) ProtoMessage() {} func (x *BatchCreateLogsRequest) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[18] + mi := &file_agent_proto_agent_proto_msgTypes[20] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1497,12 +1779,12 @@ func (x *BatchCreateLogsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use BatchCreateLogsRequest.ProtoReflect.Descriptor instead. func (*BatchCreateLogsRequest) Descriptor() ([]byte, []int) { - return file_agent_proto_agent_proto_rawDescGZIP(), []int{18} + return file_agent_proto_agent_proto_rawDescGZIP(), []int{20} } -func (x *BatchCreateLogsRequest) GetSourceId() []byte { +func (x *BatchCreateLogsRequest) GetLogSourceId() []byte { if x != nil { - return x.SourceId + return x.LogSourceId } return nil } @@ -1523,7 +1805,7 @@ type BatchCreateLogsResponse struct { func (x *BatchCreateLogsResponse) Reset() { *x = BatchCreateLogsResponse{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[19] + mi := &file_agent_proto_agent_proto_msgTypes[21] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1536,7 +1818,7 @@ func (x *BatchCreateLogsResponse) String() string { func (*BatchCreateLogsResponse) ProtoMessage() {} func (x *BatchCreateLogsResponse) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[19] + mi := &file_agent_proto_agent_proto_msgTypes[21] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1549,36 +1831,36 @@ func (x *BatchCreateLogsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use BatchCreateLogsResponse.ProtoReflect.Descriptor instead. func (*BatchCreateLogsResponse) Descriptor() ([]byte, []int) { - return file_agent_proto_agent_proto_rawDescGZIP(), []int{19} + return file_agent_proto_agent_proto_rawDescGZIP(), []int{21} } -type WorkspaceApp_HealthCheck struct { +type WorkspaceApp_Healthcheck struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Url string `protobuf:"bytes,1,opt,name=url,proto3" json:"url,omitempty"` - Interval int32 `protobuf:"varint,2,opt,name=interval,proto3" json:"interval,omitempty"` - Threshold int32 `protobuf:"varint,3,opt,name=threshold,proto3" json:"threshold,omitempty"` + Url string `protobuf:"bytes,1,opt,name=url,proto3" json:"url,omitempty"` + Interval *durationpb.Duration `protobuf:"bytes,2,opt,name=interval,proto3" json:"interval,omitempty"` + Threshold int32 `protobuf:"varint,3,opt,name=threshold,proto3" json:"threshold,omitempty"` } -func (x *WorkspaceApp_HealthCheck) Reset() { - *x = WorkspaceApp_HealthCheck{} +func (x *WorkspaceApp_Healthcheck) Reset() { + *x = WorkspaceApp_Healthcheck{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[20] + mi := &file_agent_proto_agent_proto_msgTypes[22] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } -func (x *WorkspaceApp_HealthCheck) String() string { +func (x *WorkspaceApp_Healthcheck) String() string { return protoimpl.X.MessageStringOf(x) } -func (*WorkspaceApp_HealthCheck) ProtoMessage() {} +func (*WorkspaceApp_Healthcheck) ProtoMessage() {} -func (x *WorkspaceApp_HealthCheck) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[20] +func (x *WorkspaceApp_Healthcheck) ProtoReflect() protoreflect.Message { + mi := &file_agent_proto_agent_proto_msgTypes[22] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1589,47 +1871,197 @@ func (x *WorkspaceApp_HealthCheck) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use WorkspaceApp_HealthCheck.ProtoReflect.Descriptor instead. -func (*WorkspaceApp_HealthCheck) Descriptor() ([]byte, []int) { +// Deprecated: Use WorkspaceApp_Healthcheck.ProtoReflect.Descriptor instead. +func (*WorkspaceApp_Healthcheck) Descriptor() ([]byte, []int) { return file_agent_proto_agent_proto_rawDescGZIP(), []int{0, 0} } -func (x *WorkspaceApp_HealthCheck) GetUrl() string { +func (x *WorkspaceApp_Healthcheck) GetUrl() string { if x != nil { return x.Url } return "" } -func (x *WorkspaceApp_HealthCheck) GetInterval() int32 { +func (x *WorkspaceApp_Healthcheck) GetInterval() *durationpb.Duration { if x != nil { return x.Interval } - return 0 + return nil } -func (x *WorkspaceApp_HealthCheck) GetThreshold() int32 { +func (x *WorkspaceApp_Healthcheck) GetThreshold() int32 { if x != nil { return x.Threshold } return 0 } +type WorkspaceAgentMetadata_Result struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + CollectedAt *timestamppb.Timestamp `protobuf:"bytes,1,opt,name=collected_at,json=collectedAt,proto3" json:"collected_at,omitempty"` + Age int64 `protobuf:"varint,2,opt,name=age,proto3" json:"age,omitempty"` + Value string `protobuf:"bytes,3,opt,name=value,proto3" json:"value,omitempty"` + Error string `protobuf:"bytes,4,opt,name=error,proto3" json:"error,omitempty"` +} + +func (x *WorkspaceAgentMetadata_Result) Reset() { + *x = WorkspaceAgentMetadata_Result{} + if protoimpl.UnsafeEnabled { + mi := &file_agent_proto_agent_proto_msgTypes[23] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *WorkspaceAgentMetadata_Result) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*WorkspaceAgentMetadata_Result) ProtoMessage() {} + +func (x *WorkspaceAgentMetadata_Result) ProtoReflect() protoreflect.Message { + mi := &file_agent_proto_agent_proto_msgTypes[23] + 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 WorkspaceAgentMetadata_Result.ProtoReflect.Descriptor instead. +func (*WorkspaceAgentMetadata_Result) Descriptor() ([]byte, []int) { + return file_agent_proto_agent_proto_rawDescGZIP(), []int{2, 0} +} + +func (x *WorkspaceAgentMetadata_Result) GetCollectedAt() *timestamppb.Timestamp { + if x != nil { + return x.CollectedAt + } + return nil +} + +func (x *WorkspaceAgentMetadata_Result) GetAge() int64 { + if x != nil { + return x.Age + } + return 0 +} + +func (x *WorkspaceAgentMetadata_Result) GetValue() string { + if x != nil { + return x.Value + } + return "" +} + +func (x *WorkspaceAgentMetadata_Result) GetError() string { + if x != nil { + return x.Error + } + return "" +} + +type WorkspaceAgentMetadata_Description struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + DisplayName string `protobuf:"bytes,1,opt,name=display_name,json=displayName,proto3" json:"display_name,omitempty"` + Key string `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"` + Script string `protobuf:"bytes,3,opt,name=script,proto3" json:"script,omitempty"` + Interval *durationpb.Duration `protobuf:"bytes,4,opt,name=interval,proto3" json:"interval,omitempty"` + Timeout *durationpb.Duration `protobuf:"bytes,5,opt,name=timeout,proto3" json:"timeout,omitempty"` +} + +func (x *WorkspaceAgentMetadata_Description) Reset() { + *x = WorkspaceAgentMetadata_Description{} + if protoimpl.UnsafeEnabled { + mi := &file_agent_proto_agent_proto_msgTypes[24] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *WorkspaceAgentMetadata_Description) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*WorkspaceAgentMetadata_Description) ProtoMessage() {} + +func (x *WorkspaceAgentMetadata_Description) ProtoReflect() protoreflect.Message { + mi := &file_agent_proto_agent_proto_msgTypes[24] + 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 WorkspaceAgentMetadata_Description.ProtoReflect.Descriptor instead. +func (*WorkspaceAgentMetadata_Description) Descriptor() ([]byte, []int) { + return file_agent_proto_agent_proto_rawDescGZIP(), []int{2, 1} +} + +func (x *WorkspaceAgentMetadata_Description) GetDisplayName() string { + if x != nil { + return x.DisplayName + } + return "" +} + +func (x *WorkspaceAgentMetadata_Description) GetKey() string { + if x != nil { + return x.Key + } + return "" +} + +func (x *WorkspaceAgentMetadata_Description) GetScript() string { + if x != nil { + return x.Script + } + return "" +} + +func (x *WorkspaceAgentMetadata_Description) GetInterval() *durationpb.Duration { + if x != nil { + return x.Interval + } + return nil +} + +func (x *WorkspaceAgentMetadata_Description) GetTimeout() *durationpb.Duration { + if x != nil { + return x.Timeout + } + return nil +} + type Stats_Metric struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` - Type Stats_Metric_Type `protobuf:"varint,2,opt,name=type,proto3,enum=coder.agent.v2.Stats_Metric_Type" json:"type,omitempty"` - Value float64 `protobuf:"fixed64,3,opt,name=value,proto3" json:"value,omitempty"` - Labels map[string]string `protobuf:"bytes,4,rep,name=labels,proto3" json:"labels,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Type Stats_Metric_Type `protobuf:"varint,2,opt,name=type,proto3,enum=coder.agent.v2.Stats_Metric_Type" json:"type,omitempty"` + Value float64 `protobuf:"fixed64,3,opt,name=value,proto3" json:"value,omitempty"` + Labels []*Stats_Metric_Label `protobuf:"bytes,4,rep,name=labels,proto3" json:"labels,omitempty"` } func (x *Stats_Metric) Reset() { *x = Stats_Metric{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[22] + mi := &file_agent_proto_agent_proto_msgTypes[27] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1642,7 +2074,7 @@ func (x *Stats_Metric) String() string { func (*Stats_Metric) ProtoMessage() {} func (x *Stats_Metric) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[22] + mi := &file_agent_proto_agent_proto_msgTypes[27] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1655,7 +2087,7 @@ func (x *Stats_Metric) ProtoReflect() protoreflect.Message { // Deprecated: Use Stats_Metric.ProtoReflect.Descriptor instead. func (*Stats_Metric) Descriptor() ([]byte, []int) { - return file_agent_proto_agent_proto_rawDescGZIP(), []int{5, 1} + return file_agent_proto_agent_proto_rawDescGZIP(), []int{7, 1} } func (x *Stats_Metric) GetName() string { @@ -1679,26 +2111,81 @@ func (x *Stats_Metric) GetValue() float64 { return 0 } -func (x *Stats_Metric) GetLabels() map[string]string { +func (x *Stats_Metric) GetLabels() []*Stats_Metric_Label { if x != nil { return x.Labels } return nil } +type Stats_Metric_Label struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Value string `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` +} + +func (x *Stats_Metric_Label) Reset() { + *x = Stats_Metric_Label{} + if protoimpl.UnsafeEnabled { + mi := &file_agent_proto_agent_proto_msgTypes[28] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Stats_Metric_Label) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Stats_Metric_Label) ProtoMessage() {} + +func (x *Stats_Metric_Label) ProtoReflect() protoreflect.Message { + mi := &file_agent_proto_agent_proto_msgTypes[28] + 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 Stats_Metric_Label.ProtoReflect.Descriptor instead. +func (*Stats_Metric_Label) Descriptor() ([]byte, []int) { + return file_agent_proto_agent_proto_rawDescGZIP(), []int{7, 1, 0} +} + +func (x *Stats_Metric_Label) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *Stats_Metric_Label) GetValue() string { + if x != nil { + return x.Value + } + return "" +} + type BatchUpdateAppHealthRequest_HealthUpdate struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Uuid []byte `protobuf:"bytes,1,opt,name=uuid,proto3" json:"uuid,omitempty"` + Id []byte `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` Health AppHealth `protobuf:"varint,2,opt,name=health,proto3,enum=coder.agent.v2.AppHealth" json:"health,omitempty"` } func (x *BatchUpdateAppHealthRequest_HealthUpdate) Reset() { *x = BatchUpdateAppHealthRequest_HealthUpdate{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[24] + mi := &file_agent_proto_agent_proto_msgTypes[29] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1711,7 +2198,7 @@ func (x *BatchUpdateAppHealthRequest_HealthUpdate) String() string { func (*BatchUpdateAppHealthRequest_HealthUpdate) ProtoMessage() {} func (x *BatchUpdateAppHealthRequest_HealthUpdate) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[24] + mi := &file_agent_proto_agent_proto_msgTypes[29] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1724,12 +2211,12 @@ func (x *BatchUpdateAppHealthRequest_HealthUpdate) ProtoReflect() protoreflect.M // Deprecated: Use BatchUpdateAppHealthRequest_HealthUpdate.ProtoReflect.Descriptor instead. func (*BatchUpdateAppHealthRequest_HealthUpdate) Descriptor() ([]byte, []int) { - return file_agent_proto_agent_proto_rawDescGZIP(), []int{10, 0} + return file_agent_proto_agent_proto_rawDescGZIP(), []int{12, 0} } -func (x *BatchUpdateAppHealthRequest_HealthUpdate) GetUuid() []byte { +func (x *BatchUpdateAppHealthRequest_HealthUpdate) GetId() []byte { if x != nil { - return x.Uuid + return x.Id } return nil } @@ -1752,299 +2239,398 @@ var file_agent_proto_agent_proto_rawDesc = []byte{ 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xe5, 0x05, 0x0a, 0x0c, 0x57, 0x6f, 0x72, 0x6b, - 0x73, 0x70, 0x61, 0x63, 0x65, 0x41, 0x70, 0x70, 0x12, 0x12, 0x0a, 0x04, 0x75, 0x75, 0x69, 0x64, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x75, 0x75, 0x69, 0x64, 0x12, 0x10, 0x0a, 0x03, - 0x75, 0x72, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x1a, - 0x0a, 0x08, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x08, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x6c, - 0x75, 0x67, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x73, 0x6c, 0x75, 0x67, 0x12, 0x21, - 0x0a, 0x0c, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x05, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x4e, 0x61, 0x6d, - 0x65, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x18, 0x06, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x69, - 0x63, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x69, 0x63, 0x6f, 0x6e, 0x12, - 0x1c, 0x0a, 0x09, 0x73, 0x75, 0x62, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x08, 0x20, 0x01, - 0x28, 0x08, 0x52, 0x09, 0x73, 0x75, 0x62, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x25, 0x0a, - 0x0e, 0x73, 0x75, 0x62, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, - 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x73, 0x75, 0x62, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, - 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x4e, 0x0a, 0x0d, 0x73, 0x68, 0x61, 0x72, 0x69, 0x6e, 0x67, 0x5f, - 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x29, 0x2e, 0x63, 0x6f, - 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x57, 0x6f, 0x72, - 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x41, 0x70, 0x70, 0x2e, 0x53, 0x68, 0x61, 0x72, 0x69, 0x6e, - 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x0c, 0x73, 0x68, 0x61, 0x72, 0x69, 0x6e, 0x67, 0x4c, - 0x65, 0x76, 0x65, 0x6c, 0x12, 0x4a, 0x0a, 0x0b, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, 0x68, - 0x65, 0x63, 0x6b, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x63, 0x6f, 0x64, 0x65, - 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x73, - 0x70, 0x61, 0x63, 0x65, 0x41, 0x70, 0x70, 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, - 0x65, 0x63, 0x6b, 0x52, 0x0b, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, - 0x12, 0x3b, 0x0a, 0x06, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0e, - 0x32, 0x23, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, - 0x32, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x41, 0x70, 0x70, 0x2e, 0x48, - 0x65, 0x61, 0x6c, 0x74, 0x68, 0x52, 0x06, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x1a, 0x59, 0x0a, - 0x0b, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x12, 0x10, 0x0a, 0x03, - 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x1a, - 0x0a, 0x08, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, - 0x52, 0x08, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x68, - 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x74, - 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x22, 0x57, 0x0a, 0x0c, 0x53, 0x68, 0x61, 0x72, - 0x69, 0x6e, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x1d, 0x0a, 0x19, 0x53, 0x48, 0x41, 0x52, - 0x49, 0x4e, 0x47, 0x5f, 0x4c, 0x45, 0x56, 0x45, 0x4c, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, - 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x4f, 0x57, 0x4e, 0x45, 0x52, - 0x10, 0x01, 0x12, 0x11, 0x0a, 0x0d, 0x41, 0x55, 0x54, 0x48, 0x45, 0x4e, 0x54, 0x49, 0x43, 0x41, - 0x54, 0x45, 0x44, 0x10, 0x02, 0x12, 0x0a, 0x0a, 0x06, 0x50, 0x55, 0x42, 0x4c, 0x49, 0x43, 0x10, - 0x03, 0x22, 0x5c, 0x0a, 0x06, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x12, 0x16, 0x0a, 0x12, 0x48, - 0x45, 0x41, 0x4c, 0x54, 0x48, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, - 0x44, 0x10, 0x00, 0x12, 0x0c, 0x0a, 0x08, 0x44, 0x49, 0x53, 0x41, 0x42, 0x4c, 0x45, 0x44, 0x10, - 0x01, 0x12, 0x10, 0x0a, 0x0c, 0x49, 0x4e, 0x49, 0x54, 0x49, 0x41, 0x4c, 0x49, 0x5a, 0x49, 0x4e, - 0x47, 0x10, 0x02, 0x12, 0x0b, 0x0a, 0x07, 0x48, 0x45, 0x41, 0x4c, 0x54, 0x48, 0x59, 0x10, 0x03, - 0x12, 0x0d, 0x0a, 0x09, 0x55, 0x4e, 0x48, 0x45, 0x41, 0x4c, 0x54, 0x48, 0x59, 0x10, 0x04, 0x22, - 0xd0, 0x01, 0x0a, 0x08, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x12, 0x28, 0x0a, 0x10, - 0x67, 0x69, 0x74, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x67, 0x69, 0x74, 0x41, 0x75, 0x74, 0x68, 0x43, - 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, 0x12, 0x32, 0x0a, 0x16, 0x76, 0x73, 0x5f, 0x63, 0x6f, 0x64, - 0x65, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x5f, 0x75, 0x72, 0x69, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x76, 0x73, 0x43, 0x6f, 0x64, 0x65, 0x50, 0x6f, - 0x72, 0x74, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x55, 0x72, 0x69, 0x12, 0x30, 0x0a, 0x04, 0x61, 0x70, - 0x70, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, - 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, - 0x61, 0x63, 0x65, 0x41, 0x70, 0x70, 0x52, 0x04, 0x61, 0x70, 0x70, 0x73, 0x12, 0x34, 0x0a, 0x08, - 0x64, 0x65, 0x72, 0x70, 0x5f, 0x6d, 0x61, 0x70, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, - 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, - 0x32, 0x2e, 0x44, 0x45, 0x52, 0x50, 0x4d, 0x61, 0x70, 0x52, 0x07, 0x64, 0x65, 0x72, 0x70, 0x4d, - 0x61, 0x70, 0x22, 0x14, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, - 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x6e, 0x0a, 0x0d, 0x53, 0x65, 0x72, 0x76, - 0x69, 0x63, 0x65, 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, - 0x62, 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, - 0x6c, 0x65, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x29, 0x0a, - 0x10, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x63, 0x6f, 0x6c, 0x6f, - 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, - 0x75, 0x6e, 0x64, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x22, 0x19, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x53, - 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x22, 0x89, 0x07, 0x0a, 0x05, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x5f, 0x0a, - 0x14, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x5f, 0x62, 0x79, 0x5f, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x63, 0x6f, - 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x74, 0x61, - 0x74, 0x73, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x42, 0x79, - 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x12, 0x63, 0x6f, 0x6e, 0x6e, - 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x42, 0x79, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x29, - 0x0a, 0x10, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x6f, 0x75, - 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, - 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x3f, 0x0a, 0x1c, 0x63, 0x6f, 0x6e, - 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x6e, 0x5f, 0x6c, - 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x6d, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x01, 0x52, - 0x19, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x65, 0x64, 0x69, 0x61, - 0x6e, 0x4c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x4d, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x78, - 0x5f, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, - 0x72, 0x78, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x73, 0x12, 0x19, 0x0a, 0x08, 0x72, 0x78, 0x5f, - 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x72, 0x78, 0x42, - 0x79, 0x74, 0x65, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x74, 0x78, 0x5f, 0x70, 0x61, 0x63, 0x6b, 0x65, - 0x74, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x74, 0x78, 0x50, 0x61, 0x63, 0x6b, - 0x65, 0x74, 0x73, 0x12, 0x19, 0x0a, 0x08, 0x74, 0x78, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, - 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x74, 0x78, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x30, - 0x0a, 0x14, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, - 0x76, 0x73, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x12, 0x73, 0x65, - 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x56, 0x73, 0x63, 0x6f, 0x64, 0x65, - 0x12, 0x36, 0x0a, 0x17, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x6f, 0x75, 0x6e, - 0x74, 0x5f, 0x6a, 0x65, 0x74, 0x62, 0x72, 0x61, 0x69, 0x6e, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x15, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x4a, - 0x65, 0x74, 0x62, 0x72, 0x61, 0x69, 0x6e, 0x73, 0x12, 0x43, 0x0a, 0x1e, 0x73, 0x65, 0x73, 0x73, - 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x72, 0x65, 0x63, 0x6f, 0x6e, 0x6e, - 0x65, 0x63, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x70, 0x74, 0x79, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x1b, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, - 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6e, 0x67, 0x50, 0x74, 0x79, 0x12, 0x2a, 0x0a, - 0x11, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x73, - 0x73, 0x68, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, - 0x6e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x53, 0x73, 0x68, 0x1a, 0x45, 0x0a, 0x17, 0x43, 0x6f, 0x6e, - 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x42, 0x79, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x45, - 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, - 0x1a, 0x9c, 0x02, 0x0a, 0x06, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x12, 0x12, 0x0a, 0x04, 0x6e, - 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, - 0x35, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x21, 0x2e, - 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x53, - 0x74, 0x61, 0x74, 0x73, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x2e, 0x54, 0x79, 0x70, 0x65, - 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x01, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x40, 0x0a, 0x06, - 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x63, - 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x74, - 0x61, 0x74, 0x73, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x2e, 0x4c, 0x61, 0x62, 0x65, 0x6c, - 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x1a, 0x39, - 0x0a, 0x0b, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, - 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, - 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, - 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x34, 0x0a, 0x04, 0x54, 0x79, 0x70, - 0x65, 0x12, 0x14, 0x0a, 0x10, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, - 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x43, 0x4f, 0x55, 0x4e, 0x54, - 0x45, 0x52, 0x10, 0x01, 0x12, 0x09, 0x0a, 0x05, 0x47, 0x41, 0x55, 0x47, 0x45, 0x10, 0x02, 0x22, - 0x41, 0x0a, 0x12, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2b, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x73, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, - 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x05, 0x73, 0x74, 0x61, - 0x74, 0x73, 0x22, 0x70, 0x0a, 0x13, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, - 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x59, 0x0a, 0x1b, 0x72, 0x65, 0x70, - 0x6f, 0x72, 0x74, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x5f, 0x6e, 0x61, 0x6e, - 0x6f, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, - 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, - 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x19, 0x72, 0x65, 0x70, 0x6f, 0x72, - 0x74, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x4e, 0x61, 0x6e, 0x6f, 0x73, 0x65, 0x63, - 0x6f, 0x6e, 0x64, 0x73, 0x22, 0xf2, 0x01, 0x0a, 0x09, 0x4c, 0x69, 0x66, 0x65, 0x63, 0x79, 0x63, - 0x6c, 0x65, 0x12, 0x35, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0e, 0x32, 0x1f, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, - 0x76, 0x32, 0x2e, 0x4c, 0x69, 0x66, 0x65, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x2e, 0x53, 0x74, 0x61, - 0x74, 0x65, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x22, 0xad, 0x01, 0x0a, 0x05, 0x53, 0x74, - 0x61, 0x74, 0x65, 0x12, 0x15, 0x0a, 0x11, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x55, 0x4e, 0x53, - 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x43, 0x52, - 0x45, 0x41, 0x54, 0x45, 0x44, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x53, 0x54, 0x41, 0x52, 0x54, - 0x45, 0x44, 0x10, 0x02, 0x12, 0x11, 0x0a, 0x0d, 0x53, 0x54, 0x41, 0x52, 0x54, 0x5f, 0x54, 0x49, - 0x4d, 0x45, 0x4f, 0x55, 0x54, 0x10, 0x03, 0x12, 0x0f, 0x0a, 0x0b, 0x53, 0x54, 0x41, 0x52, 0x54, - 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x04, 0x12, 0x09, 0x0a, 0x05, 0x52, 0x45, 0x41, 0x44, - 0x59, 0x10, 0x05, 0x12, 0x11, 0x0a, 0x0d, 0x53, 0x48, 0x55, 0x54, 0x54, 0x49, 0x4e, 0x47, 0x5f, - 0x44, 0x4f, 0x57, 0x4e, 0x10, 0x06, 0x12, 0x14, 0x0a, 0x10, 0x53, 0x48, 0x55, 0x54, 0x44, 0x4f, - 0x57, 0x4e, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x4f, 0x55, 0x54, 0x10, 0x07, 0x12, 0x12, 0x0a, 0x0e, - 0x53, 0x48, 0x55, 0x54, 0x44, 0x4f, 0x57, 0x4e, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x08, - 0x12, 0x07, 0x0a, 0x03, 0x4f, 0x46, 0x46, 0x10, 0x09, 0x22, 0x51, 0x0a, 0x16, 0x55, 0x70, 0x64, - 0x61, 0x74, 0x65, 0x4c, 0x69, 0x66, 0x65, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x37, 0x0a, 0x09, 0x6c, 0x69, 0x66, 0x65, 0x63, 0x79, 0x63, 0x6c, 0x65, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, - 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x4c, 0x69, 0x66, 0x65, 0x63, 0x79, 0x63, 0x6c, - 0x65, 0x52, 0x09, 0x6c, 0x69, 0x66, 0x65, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x22, 0xc8, 0x01, 0x0a, - 0x1b, 0x42, 0x61, 0x74, 0x63, 0x68, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x70, 0x70, 0x48, - 0x65, 0x61, 0x6c, 0x74, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x52, 0x0a, 0x07, - 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x38, 0x2e, - 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x42, - 0x61, 0x74, 0x63, 0x68, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x70, 0x70, 0x48, 0x65, 0x61, - 0x6c, 0x74, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74, - 0x68, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x07, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, - 0x1a, 0x55, 0x0a, 0x0c, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, - 0x12, 0x12, 0x0a, 0x04, 0x75, 0x75, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, - 0x75, 0x75, 0x69, 0x64, 0x12, 0x31, 0x0a, 0x06, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x0e, 0x32, 0x19, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, - 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x41, 0x70, 0x70, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x52, - 0x06, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x22, 0x1e, 0x0a, 0x1c, 0x42, 0x61, 0x74, 0x63, 0x68, - 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x70, 0x70, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x72, 0x0a, 0x07, 0x53, 0x74, 0x61, 0x72, 0x74, - 0x75, 0x70, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x2d, 0x0a, 0x12, - 0x65, 0x78, 0x70, 0x61, 0x6e, 0x64, 0x65, 0x64, 0x5f, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, - 0x72, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x65, 0x78, 0x70, 0x61, 0x6e, 0x64, - 0x65, 0x64, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x1e, 0x0a, 0x0a, 0x73, - 0x75, 0x62, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, - 0x0a, 0x73, 0x75, 0x62, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x73, 0x22, 0x49, 0x0a, 0x14, 0x55, - 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x31, 0x0a, 0x07, 0x73, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, - 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x52, 0x07, 0x73, - 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x22, 0x99, 0x01, 0x0a, 0x08, 0x4d, 0x65, 0x74, 0x61, 0x64, - 0x61, 0x74, 0x61, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x3d, 0x0a, 0x0c, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, - 0x65, 0x64, 0x5f, 0x61, 0x74, 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, 0x0b, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, - 0x65, 0x64, 0x41, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x61, 0x67, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x03, 0x61, 0x67, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x14, 0x0a, 0x05, - 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, - 0x6f, 0x72, 0x22, 0x52, 0x0a, 0x1a, 0x42, 0x61, 0x74, 0x63, 0x68, 0x55, 0x70, 0x64, 0x61, 0x74, - 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x34, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, - 0x2e, 0x76, 0x32, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, - 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x22, 0x1d, 0x0a, 0x1b, 0x42, 0x61, 0x74, 0x63, 0x68, 0x55, - 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xde, 0x01, 0x0a, 0x03, 0x4c, 0x6f, 0x67, 0x12, 0x39, 0x0a, - 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x01, 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, 0x09, 0x63, - 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x75, 0x74, 0x70, - 0x75, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, - 0x12, 0x2f, 0x0a, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, - 0x19, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, - 0x2e, 0x4c, 0x6f, 0x67, 0x2e, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x05, 0x6c, 0x65, 0x76, 0x65, - 0x6c, 0x22, 0x53, 0x0a, 0x05, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x15, 0x0a, 0x11, 0x4c, 0x45, - 0x56, 0x45, 0x4c, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, - 0x00, 0x12, 0x09, 0x0a, 0x05, 0x54, 0x52, 0x41, 0x43, 0x45, 0x10, 0x01, 0x12, 0x09, 0x0a, 0x05, - 0x44, 0x45, 0x42, 0x55, 0x47, 0x10, 0x02, 0x12, 0x08, 0x0a, 0x04, 0x49, 0x4e, 0x46, 0x4f, 0x10, - 0x03, 0x12, 0x08, 0x0a, 0x04, 0x57, 0x41, 0x52, 0x4e, 0x10, 0x04, 0x12, 0x09, 0x0a, 0x05, 0x45, - 0x52, 0x52, 0x4f, 0x52, 0x10, 0x05, 0x22, 0x5e, 0x0a, 0x16, 0x42, 0x61, 0x74, 0x63, 0x68, 0x43, - 0x72, 0x65, 0x61, 0x74, 0x65, 0x4c, 0x6f, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x1b, 0x0a, 0x09, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0c, 0x52, 0x08, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x64, 0x12, 0x27, 0x0a, - 0x04, 0x6c, 0x6f, 0x67, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x63, 0x6f, - 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x4c, 0x6f, 0x67, - 0x52, 0x04, 0x6c, 0x6f, 0x67, 0x73, 0x22, 0x19, 0x0a, 0x17, 0x42, 0x61, 0x74, 0x63, 0x68, 0x43, - 0x72, 0x65, 0x61, 0x74, 0x65, 0x4c, 0x6f, 0x67, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x2a, 0x63, 0x0a, 0x09, 0x41, 0x70, 0x70, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x12, 0x1a, - 0x0a, 0x16, 0x41, 0x50, 0x50, 0x5f, 0x48, 0x45, 0x41, 0x4c, 0x54, 0x48, 0x5f, 0x55, 0x4e, 0x53, + 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xfc, 0x05, 0x0a, 0x0c, 0x57, 0x6f, 0x72, 0x6b, + 0x73, 0x70, 0x61, 0x63, 0x65, 0x41, 0x70, 0x70, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, + 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x65, 0x78, + 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x6c, 0x75, 0x67, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x73, 0x6c, 0x75, 0x67, 0x12, 0x21, 0x0a, 0x0c, 0x64, 0x69, + 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0b, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x18, 0x0a, + 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, + 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x69, 0x63, 0x6f, 0x6e, 0x18, + 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x69, 0x63, 0x6f, 0x6e, 0x12, 0x1c, 0x0a, 0x09, 0x73, + 0x75, 0x62, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, + 0x73, 0x75, 0x62, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x25, 0x0a, 0x0e, 0x73, 0x75, 0x62, + 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0d, 0x73, 0x75, 0x62, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x4e, 0x61, 0x6d, 0x65, + 0x12, 0x4e, 0x0a, 0x0d, 0x73, 0x68, 0x61, 0x72, 0x69, 0x6e, 0x67, 0x5f, 0x6c, 0x65, 0x76, 0x65, + 0x6c, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x29, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, + 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, + 0x63, 0x65, 0x41, 0x70, 0x70, 0x2e, 0x53, 0x68, 0x61, 0x72, 0x69, 0x6e, 0x67, 0x4c, 0x65, 0x76, + 0x65, 0x6c, 0x52, 0x0c, 0x73, 0x68, 0x61, 0x72, 0x69, 0x6e, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, + 0x12, 0x4a, 0x0a, 0x0b, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x18, + 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, + 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, + 0x41, 0x70, 0x70, 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x52, + 0x0b, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x12, 0x3b, 0x0a, 0x06, + 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x23, 0x2e, 0x63, + 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x57, 0x6f, + 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x41, 0x70, 0x70, 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74, + 0x68, 0x52, 0x06, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x1a, 0x74, 0x0a, 0x0b, 0x48, 0x65, 0x61, + 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x35, 0x0a, 0x08, 0x69, 0x6e, + 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, + 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x08, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, + 0x6c, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x74, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x22, + 0x57, 0x0a, 0x0c, 0x53, 0x68, 0x61, 0x72, 0x69, 0x6e, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, + 0x1d, 0x0a, 0x19, 0x53, 0x48, 0x41, 0x52, 0x49, 0x4e, 0x47, 0x5f, 0x4c, 0x45, 0x56, 0x45, 0x4c, + 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x09, + 0x0a, 0x05, 0x4f, 0x57, 0x4e, 0x45, 0x52, 0x10, 0x01, 0x12, 0x11, 0x0a, 0x0d, 0x41, 0x55, 0x54, + 0x48, 0x45, 0x4e, 0x54, 0x49, 0x43, 0x41, 0x54, 0x45, 0x44, 0x10, 0x02, 0x12, 0x0a, 0x0a, 0x06, + 0x50, 0x55, 0x42, 0x4c, 0x49, 0x43, 0x10, 0x03, 0x22, 0x5c, 0x0a, 0x06, 0x48, 0x65, 0x61, 0x6c, + 0x74, 0x68, 0x12, 0x16, 0x0a, 0x12, 0x48, 0x45, 0x41, 0x4c, 0x54, 0x48, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0c, 0x0a, 0x08, 0x44, 0x49, 0x53, 0x41, 0x42, 0x4c, 0x45, 0x44, 0x10, 0x01, 0x12, 0x10, 0x0a, 0x0c, 0x49, 0x4e, 0x49, 0x54, 0x49, 0x41, 0x4c, 0x49, 0x5a, 0x49, 0x4e, 0x47, 0x10, 0x02, 0x12, 0x0b, 0x0a, 0x07, 0x48, 0x45, 0x41, 0x4c, 0x54, 0x48, 0x59, 0x10, 0x03, 0x12, 0x0d, 0x0a, 0x09, 0x55, 0x4e, 0x48, 0x45, 0x41, - 0x4c, 0x54, 0x48, 0x59, 0x10, 0x04, 0x32, 0xb2, 0x07, 0x0a, 0x05, 0x41, 0x67, 0x65, 0x6e, 0x74, - 0x12, 0x4b, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x12, - 0x22, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, - 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, - 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x12, 0x5a, 0x0a, - 0x10, 0x47, 0x65, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x42, 0x61, 0x6e, 0x6e, 0x65, - 0x72, 0x12, 0x27, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, - 0x76, 0x32, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x42, 0x61, 0x6e, - 0x6e, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x63, 0x6f, 0x64, - 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x65, 0x72, 0x76, - 0x69, 0x63, 0x65, 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x12, 0x56, 0x0a, 0x0b, 0x55, 0x70, 0x64, - 0x61, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x22, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, - 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, - 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x63, - 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x55, 0x70, - 0x64, 0x61, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x54, 0x0a, 0x0f, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4c, 0x69, 0x66, 0x65, 0x63, - 0x79, 0x63, 0x6c, 0x65, 0x12, 0x26, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, - 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4c, 0x69, 0x66, 0x65, - 0x63, 0x79, 0x63, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x63, - 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x4c, 0x69, - 0x66, 0x65, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x12, 0x72, 0x0a, 0x15, 0x42, 0x61, 0x74, 0x63, 0x68, - 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x70, 0x70, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x73, - 0x12, 0x2b, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, - 0x32, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x70, 0x70, - 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2c, 0x2e, - 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x42, - 0x61, 0x74, 0x63, 0x68, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x70, 0x70, 0x48, 0x65, 0x61, - 0x6c, 0x74, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4e, 0x0a, 0x0d, 0x55, - 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x12, 0x24, 0x2e, 0x63, - 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x55, 0x70, + 0x4c, 0x54, 0x48, 0x59, 0x10, 0x04, 0x22, 0xa6, 0x02, 0x0a, 0x14, 0x57, 0x6f, 0x72, 0x6b, 0x73, + 0x70, 0x61, 0x63, 0x65, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x12, + 0x22, 0x0a, 0x0d, 0x6c, 0x6f, 0x67, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x6c, 0x6f, 0x67, 0x53, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x6c, 0x6f, 0x67, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6c, 0x6f, 0x67, 0x50, 0x61, 0x74, 0x68, 0x12, 0x16, + 0x0a, 0x06, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, + 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x72, 0x6f, 0x6e, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x63, 0x72, 0x6f, 0x6e, 0x12, 0x20, 0x0a, 0x0c, 0x72, 0x75, + 0x6e, 0x5f, 0x6f, 0x6e, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x0a, 0x72, 0x75, 0x6e, 0x4f, 0x6e, 0x53, 0x74, 0x61, 0x72, 0x74, 0x12, 0x1e, 0x0a, 0x0b, + 0x72, 0x75, 0x6e, 0x5f, 0x6f, 0x6e, 0x5f, 0x73, 0x74, 0x6f, 0x70, 0x18, 0x06, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x09, 0x72, 0x75, 0x6e, 0x4f, 0x6e, 0x53, 0x74, 0x6f, 0x70, 0x12, 0x2c, 0x0a, 0x12, + 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x5f, 0x6c, 0x6f, 0x67, + 0x69, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x73, 0x74, 0x61, 0x72, 0x74, 0x42, + 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x33, 0x0a, 0x07, 0x74, 0x69, + 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, + 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x22, + 0x86, 0x04, 0x0a, 0x16, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x41, 0x67, 0x65, + 0x6e, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x45, 0x0a, 0x06, 0x72, 0x65, + 0x73, 0x75, 0x6c, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x63, 0x6f, 0x64, + 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x57, 0x6f, 0x72, 0x6b, + 0x73, 0x70, 0x61, 0x63, 0x65, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, + 0x74, 0x61, 0x2e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, + 0x74, 0x12, 0x54, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x32, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, + 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, + 0x65, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x44, + 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, + 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x85, 0x01, 0x0a, 0x06, 0x52, 0x65, 0x73, 0x75, + 0x6c, 0x74, 0x12, 0x3d, 0x0a, 0x0c, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x65, 0x64, 0x5f, + 0x61, 0x74, 0x18, 0x01, 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, 0x0b, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x65, 0x64, 0x41, + 0x74, 0x12, 0x10, 0x0a, 0x03, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x03, + 0x61, 0x67, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, + 0x6f, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x1a, + 0xc6, 0x01, 0x0a, 0x0b, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, + 0x21, 0x0a, 0x0c, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x4e, 0x61, + 0x6d, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x03, 0x6b, 0x65, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x12, 0x35, 0x0a, 0x08, + 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, + 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, + 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x08, 0x69, 0x6e, 0x74, 0x65, 0x72, + 0x76, 0x61, 0x6c, 0x12, 0x33, 0x0a, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, + 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x22, 0xa4, 0x06, 0x0a, 0x08, 0x4d, 0x61, 0x6e, + 0x69, 0x66, 0x65, 0x73, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x5f, 0x69, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x49, 0x64, + 0x12, 0x25, 0x0a, 0x0e, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, + 0x6d, 0x65, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x55, + 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x77, 0x6f, 0x72, 0x6b, 0x73, + 0x70, 0x61, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x77, + 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x49, 0x64, 0x12, 0x28, 0x0a, 0x10, 0x67, 0x69, + 0x74, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x67, 0x69, 0x74, 0x41, 0x75, 0x74, 0x68, 0x43, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x73, 0x12, 0x67, 0x0a, 0x15, 0x65, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, + 0x65, 0x6e, 0x74, 0x5f, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x18, 0x03, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x32, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, + 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x2e, 0x45, 0x6e, + 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, + 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x14, 0x65, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, + 0x6d, 0x65, 0x6e, 0x74, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x12, 0x1c, 0x0a, + 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x32, 0x0a, 0x16, 0x76, + 0x73, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x70, 0x72, 0x6f, 0x78, + 0x79, 0x5f, 0x75, 0x72, 0x69, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x76, 0x73, 0x43, + 0x6f, 0x64, 0x65, 0x50, 0x6f, 0x72, 0x74, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x55, 0x72, 0x69, 0x12, + 0x1b, 0x0a, 0x09, 0x6d, 0x6f, 0x74, 0x64, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x06, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x08, 0x6d, 0x6f, 0x74, 0x64, 0x50, 0x61, 0x74, 0x68, 0x12, 0x3c, 0x0a, 0x1a, + 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x5f, 0x63, + 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x18, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x43, + 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x32, 0x0a, 0x15, 0x64, 0x65, + 0x72, 0x70, 0x5f, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x5f, 0x77, 0x65, 0x62, 0x73, 0x6f, 0x63, 0x6b, + 0x65, 0x74, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x13, 0x64, 0x65, 0x72, 0x70, 0x46, + 0x6f, 0x72, 0x63, 0x65, 0x57, 0x65, 0x62, 0x73, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x73, 0x12, 0x34, + 0x0a, 0x08, 0x64, 0x65, 0x72, 0x70, 0x5f, 0x6d, 0x61, 0x70, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x19, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, + 0x2e, 0x76, 0x32, 0x2e, 0x44, 0x45, 0x52, 0x50, 0x4d, 0x61, 0x70, 0x52, 0x07, 0x64, 0x65, 0x72, + 0x70, 0x4d, 0x61, 0x70, 0x12, 0x3e, 0x0a, 0x07, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x73, 0x18, + 0x0a, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, + 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, + 0x41, 0x67, 0x65, 0x6e, 0x74, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x52, 0x07, 0x73, 0x63, 0x72, + 0x69, 0x70, 0x74, 0x73, 0x12, 0x30, 0x0a, 0x04, 0x61, 0x70, 0x70, 0x73, 0x18, 0x0b, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, + 0x2e, 0x76, 0x32, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x41, 0x70, 0x70, + 0x52, 0x04, 0x61, 0x70, 0x70, 0x73, 0x12, 0x4e, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, + 0x74, 0x61, 0x18, 0x0c, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x32, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, + 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, + 0x61, 0x63, 0x65, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, + 0x2e, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x08, 0x6d, 0x65, + 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x1a, 0x47, 0x0a, 0x19, 0x45, 0x6e, 0x76, 0x69, 0x72, 0x6f, + 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x45, 0x6e, + 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, + 0x14, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x6e, 0x0a, 0x0d, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, + 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, + 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x62, 0x61, + 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, + 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x22, 0x19, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x53, 0x65, 0x72, 0x76, + 0x69, 0x63, 0x65, 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x22, 0xb3, 0x07, 0x0a, 0x05, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x5f, 0x0a, 0x14, 0x63, 0x6f, + 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x5f, 0x62, 0x79, 0x5f, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, + 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x73, 0x2e, + 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x42, 0x79, 0x50, 0x72, 0x6f, + 0x74, 0x6f, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x12, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x73, 0x42, 0x79, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x29, 0x0a, 0x10, 0x63, + 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x3f, 0x0a, 0x1c, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x6e, 0x5f, 0x6c, 0x61, 0x74, 0x65, + 0x6e, 0x63, 0x79, 0x5f, 0x6d, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x01, 0x52, 0x19, 0x63, 0x6f, + 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x65, 0x64, 0x69, 0x61, 0x6e, 0x4c, 0x61, + 0x74, 0x65, 0x6e, 0x63, 0x79, 0x4d, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x78, 0x5f, 0x70, 0x61, + 0x63, 0x6b, 0x65, 0x74, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x72, 0x78, 0x50, + 0x61, 0x63, 0x6b, 0x65, 0x74, 0x73, 0x12, 0x19, 0x0a, 0x08, 0x72, 0x78, 0x5f, 0x62, 0x79, 0x74, + 0x65, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x72, 0x78, 0x42, 0x79, 0x74, 0x65, + 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x74, 0x78, 0x5f, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x73, 0x18, + 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x74, 0x78, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x73, + 0x12, 0x19, 0x0a, 0x08, 0x74, 0x78, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x07, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x07, 0x74, 0x78, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x30, 0x0a, 0x14, 0x73, + 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x76, 0x73, 0x63, + 0x6f, 0x64, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x12, 0x73, 0x65, 0x73, 0x73, 0x69, + 0x6f, 0x6e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x56, 0x73, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x36, 0x0a, + 0x17, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x6a, + 0x65, 0x74, 0x62, 0x72, 0x61, 0x69, 0x6e, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x15, + 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x4a, 0x65, 0x74, 0x62, + 0x72, 0x61, 0x69, 0x6e, 0x73, 0x12, 0x43, 0x0a, 0x1e, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, + 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x72, 0x65, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, + 0x69, 0x6e, 0x67, 0x5f, 0x70, 0x74, 0x79, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x1b, 0x73, + 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x6e, + 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6e, 0x67, 0x50, 0x74, 0x79, 0x12, 0x2a, 0x0a, 0x11, 0x73, 0x65, + 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x73, 0x73, 0x68, 0x18, + 0x0b, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, + 0x75, 0x6e, 0x74, 0x53, 0x73, 0x68, 0x12, 0x36, 0x0a, 0x07, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, + 0x73, 0x18, 0x0c, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, + 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x4d, + 0x65, 0x74, 0x72, 0x69, 0x63, 0x52, 0x07, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x1a, 0x45, + 0x0a, 0x17, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x42, 0x79, 0x50, + 0x72, 0x6f, 0x74, 0x6f, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x8e, 0x02, 0x0a, 0x06, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, + 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, + 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x35, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0e, 0x32, 0x21, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, + 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, + 0x2e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x01, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x12, 0x3a, 0x0a, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x22, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, + 0x76, 0x32, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x2e, + 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x52, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x1a, 0x31, 0x0a, + 0x05, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x22, 0x34, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x10, 0x54, 0x59, 0x50, 0x45, + 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0b, + 0x0a, 0x07, 0x43, 0x4f, 0x55, 0x4e, 0x54, 0x45, 0x52, 0x10, 0x01, 0x12, 0x09, 0x0a, 0x05, 0x47, + 0x41, 0x55, 0x47, 0x45, 0x10, 0x02, 0x22, 0x41, 0x0a, 0x12, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2b, 0x0a, 0x05, + 0x73, 0x74, 0x61, 0x74, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x63, 0x6f, + 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x74, 0x61, + 0x74, 0x73, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x73, 0x22, 0x59, 0x0a, 0x13, 0x55, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x42, 0x0a, 0x0f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, + 0x76, 0x61, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0e, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x49, 0x6e, 0x74, 0x65, + 0x72, 0x76, 0x61, 0x6c, 0x22, 0xae, 0x02, 0x0a, 0x09, 0x4c, 0x69, 0x66, 0x65, 0x63, 0x79, 0x63, + 0x6c, 0x65, 0x12, 0x35, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0e, 0x32, 0x1f, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, + 0x76, 0x32, 0x2e, 0x4c, 0x69, 0x66, 0x65, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x2e, 0x53, 0x74, 0x61, + 0x74, 0x65, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x39, 0x0a, 0x0a, 0x63, 0x68, 0x61, + 0x6e, 0x67, 0x65, 0x64, 0x5f, 0x61, 0x74, 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, 0x09, 0x63, 0x68, 0x61, 0x6e, 0x67, + 0x65, 0x64, 0x41, 0x74, 0x22, 0xae, 0x01, 0x0a, 0x05, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x15, + 0x0a, 0x11, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, + 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x43, 0x52, 0x45, 0x41, 0x54, 0x45, 0x44, + 0x10, 0x01, 0x12, 0x0c, 0x0a, 0x08, 0x53, 0x54, 0x41, 0x52, 0x54, 0x49, 0x4e, 0x47, 0x10, 0x02, + 0x12, 0x11, 0x0a, 0x0d, 0x53, 0x54, 0x41, 0x52, 0x54, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x4f, 0x55, + 0x54, 0x10, 0x03, 0x12, 0x0f, 0x0a, 0x0b, 0x53, 0x54, 0x41, 0x52, 0x54, 0x5f, 0x45, 0x52, 0x52, + 0x4f, 0x52, 0x10, 0x04, 0x12, 0x09, 0x0a, 0x05, 0x52, 0x45, 0x41, 0x44, 0x59, 0x10, 0x05, 0x12, + 0x11, 0x0a, 0x0d, 0x53, 0x48, 0x55, 0x54, 0x54, 0x49, 0x4e, 0x47, 0x5f, 0x44, 0x4f, 0x57, 0x4e, + 0x10, 0x06, 0x12, 0x14, 0x0a, 0x10, 0x53, 0x48, 0x55, 0x54, 0x44, 0x4f, 0x57, 0x4e, 0x5f, 0x54, + 0x49, 0x4d, 0x45, 0x4f, 0x55, 0x54, 0x10, 0x07, 0x12, 0x12, 0x0a, 0x0e, 0x53, 0x48, 0x55, 0x54, + 0x44, 0x4f, 0x57, 0x4e, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x08, 0x12, 0x07, 0x0a, 0x03, + 0x4f, 0x46, 0x46, 0x10, 0x09, 0x22, 0x51, 0x0a, 0x16, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4c, + 0x69, 0x66, 0x65, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x37, 0x0a, 0x09, 0x6c, 0x69, 0x66, 0x65, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, + 0x2e, 0x76, 0x32, 0x2e, 0x4c, 0x69, 0x66, 0x65, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x52, 0x09, 0x6c, + 0x69, 0x66, 0x65, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x22, 0xc4, 0x01, 0x0a, 0x1b, 0x42, 0x61, 0x74, + 0x63, 0x68, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x70, 0x70, 0x48, 0x65, 0x61, 0x6c, 0x74, + 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x52, 0x0a, 0x07, 0x75, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x38, 0x2e, 0x63, 0x6f, 0x64, 0x65, + 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, + 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x70, 0x70, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x55, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x52, 0x07, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x1a, 0x51, 0x0a, 0x0c, + 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x0e, 0x0a, 0x02, + 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x64, 0x12, 0x31, 0x0a, 0x06, + 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x19, 0x2e, 0x63, + 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x41, 0x70, + 0x70, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x52, 0x06, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x22, + 0x1e, 0x0a, 0x1c, 0x42, 0x61, 0x74, 0x63, 0x68, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x70, + 0x70, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0xe8, 0x01, 0x0a, 0x07, 0x53, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x12, 0x18, 0x0a, 0x07, 0x76, + 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, + 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x2d, 0x0a, 0x12, 0x65, 0x78, 0x70, 0x61, 0x6e, 0x64, 0x65, + 0x64, 0x5f, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x11, 0x65, 0x78, 0x70, 0x61, 0x6e, 0x64, 0x65, 0x64, 0x44, 0x69, 0x72, 0x65, 0x63, + 0x74, 0x6f, 0x72, 0x79, 0x12, 0x41, 0x0a, 0x0a, 0x73, 0x75, 0x62, 0x73, 0x79, 0x73, 0x74, 0x65, + 0x6d, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0e, 0x32, 0x21, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, + 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x74, 0x61, 0x72, 0x74, 0x75, + 0x70, 0x2e, 0x53, 0x75, 0x62, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x52, 0x0a, 0x73, 0x75, 0x62, + 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x73, 0x22, 0x51, 0x0a, 0x09, 0x53, 0x75, 0x62, 0x73, 0x79, + 0x73, 0x74, 0x65, 0x6d, 0x12, 0x19, 0x0a, 0x15, 0x53, 0x55, 0x42, 0x53, 0x59, 0x53, 0x54, 0x45, + 0x4d, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, + 0x0a, 0x0a, 0x06, 0x45, 0x4e, 0x56, 0x42, 0x4f, 0x58, 0x10, 0x01, 0x12, 0x0e, 0x0a, 0x0a, 0x45, + 0x4e, 0x56, 0x42, 0x55, 0x49, 0x4c, 0x44, 0x45, 0x52, 0x10, 0x02, 0x12, 0x0d, 0x0a, 0x09, 0x45, + 0x58, 0x45, 0x43, 0x54, 0x52, 0x41, 0x43, 0x45, 0x10, 0x03, 0x22, 0x49, 0x0a, 0x14, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, - 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x12, 0x6e, 0x0a, 0x13, 0x42, + 0x73, 0x74, 0x12, 0x31, 0x0a, 0x07, 0x73, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, + 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x52, 0x07, 0x73, 0x74, + 0x61, 0x72, 0x74, 0x75, 0x70, 0x22, 0x63, 0x0a, 0x08, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, + 0x61, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, + 0x6b, 0x65, 0x79, 0x12, 0x45, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, + 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x41, 0x67, + 0x65, 0x6e, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x52, 0x65, 0x73, 0x75, + 0x6c, 0x74, 0x52, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x22, 0x52, 0x0a, 0x1a, 0x42, 0x61, + 0x74, 0x63, 0x68, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, + 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x34, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, + 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x63, 0x6f, 0x64, + 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x4d, 0x65, 0x74, 0x61, + 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x22, 0x1d, + 0x0a, 0x1b, 0x42, 0x61, 0x74, 0x63, 0x68, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x65, 0x74, + 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xde, 0x01, + 0x0a, 0x03, 0x4c, 0x6f, 0x67, 0x12, 0x39, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, + 0x5f, 0x61, 0x74, 0x18, 0x01, 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, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, + 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x06, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x12, 0x2f, 0x0a, 0x05, 0x6c, 0x65, 0x76, 0x65, + 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x19, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, + 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x4c, 0x6f, 0x67, 0x2e, 0x4c, 0x65, 0x76, + 0x65, 0x6c, 0x52, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x22, 0x53, 0x0a, 0x05, 0x4c, 0x65, 0x76, + 0x65, 0x6c, 0x12, 0x15, 0x0a, 0x11, 0x4c, 0x45, 0x56, 0x45, 0x4c, 0x5f, 0x55, 0x4e, 0x53, 0x50, + 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x54, 0x52, 0x41, + 0x43, 0x45, 0x10, 0x01, 0x12, 0x09, 0x0a, 0x05, 0x44, 0x45, 0x42, 0x55, 0x47, 0x10, 0x02, 0x12, + 0x08, 0x0a, 0x04, 0x49, 0x4e, 0x46, 0x4f, 0x10, 0x03, 0x12, 0x08, 0x0a, 0x04, 0x57, 0x41, 0x52, + 0x4e, 0x10, 0x04, 0x12, 0x09, 0x0a, 0x05, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x05, 0x22, 0x65, + 0x0a, 0x16, 0x42, 0x61, 0x74, 0x63, 0x68, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4c, 0x6f, 0x67, + 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x22, 0x0a, 0x0d, 0x6c, 0x6f, 0x67, 0x5f, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, + 0x0b, 0x6c, 0x6f, 0x67, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x64, 0x12, 0x27, 0x0a, 0x04, + 0x6c, 0x6f, 0x67, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x63, 0x6f, 0x64, + 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x4c, 0x6f, 0x67, 0x52, + 0x04, 0x6c, 0x6f, 0x67, 0x73, 0x22, 0x19, 0x0a, 0x17, 0x42, 0x61, 0x74, 0x63, 0x68, 0x43, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x4c, 0x6f, 0x67, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x2a, 0x63, 0x0a, 0x09, 0x41, 0x70, 0x70, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x12, 0x1a, 0x0a, + 0x16, 0x41, 0x50, 0x50, 0x5f, 0x48, 0x45, 0x41, 0x4c, 0x54, 0x48, 0x5f, 0x55, 0x4e, 0x53, 0x50, + 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0c, 0x0a, 0x08, 0x44, 0x49, 0x53, + 0x41, 0x42, 0x4c, 0x45, 0x44, 0x10, 0x01, 0x12, 0x10, 0x0a, 0x0c, 0x49, 0x4e, 0x49, 0x54, 0x49, + 0x41, 0x4c, 0x49, 0x5a, 0x49, 0x4e, 0x47, 0x10, 0x02, 0x12, 0x0b, 0x0a, 0x07, 0x48, 0x45, 0x41, + 0x4c, 0x54, 0x48, 0x59, 0x10, 0x03, 0x12, 0x0d, 0x0a, 0x09, 0x55, 0x4e, 0x48, 0x45, 0x41, 0x4c, + 0x54, 0x48, 0x59, 0x10, 0x04, 0x32, 0xb2, 0x07, 0x0a, 0x05, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x12, + 0x4b, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x12, 0x22, + 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, + 0x47, 0x65, 0x74, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, + 0x2e, 0x76, 0x32, 0x2e, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x12, 0x5a, 0x0a, 0x10, + 0x47, 0x65, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x72, + 0x12, 0x27, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, + 0x32, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x42, 0x61, 0x6e, 0x6e, + 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x63, 0x6f, 0x64, 0x65, + 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, + 0x63, 0x65, 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x12, 0x56, 0x0a, 0x0b, 0x55, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x22, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, + 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, + 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x63, 0x6f, + 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x55, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x54, 0x0a, 0x0f, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4c, 0x69, 0x66, 0x65, 0x63, 0x79, + 0x63, 0x6c, 0x65, 0x12, 0x26, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, + 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4c, 0x69, 0x66, 0x65, 0x63, + 0x79, 0x63, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x63, 0x6f, + 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x4c, 0x69, 0x66, + 0x65, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x12, 0x72, 0x0a, 0x15, 0x42, 0x61, 0x74, 0x63, 0x68, 0x55, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x70, 0x70, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x73, 0x12, + 0x2b, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, + 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x70, 0x70, 0x48, + 0x65, 0x61, 0x6c, 0x74, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2c, 0x2e, 0x63, + 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x61, + 0x74, 0x63, 0x68, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x70, 0x70, 0x48, 0x65, 0x61, 0x6c, + 0x74, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4e, 0x0a, 0x0d, 0x55, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x53, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x12, 0x24, 0x2e, 0x63, 0x6f, + 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x55, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x53, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x17, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, + 0x76, 0x32, 0x2e, 0x53, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x12, 0x6e, 0x0a, 0x13, 0x42, 0x61, + 0x74, 0x63, 0x68, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, + 0x61, 0x12, 0x2a, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, + 0x76, 0x32, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x65, + 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, + 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, - 0x74, 0x61, 0x12, 0x2a, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, - 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, - 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, - 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, - 0x42, 0x61, 0x74, 0x63, 0x68, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, - 0x61, 0x74, 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x62, 0x0a, 0x0f, 0x42, - 0x61, 0x74, 0x63, 0x68, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4c, 0x6f, 0x67, 0x73, 0x12, 0x26, - 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, - 0x42, 0x61, 0x74, 0x63, 0x68, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4c, 0x6f, 0x67, 0x73, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, - 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x43, 0x72, 0x65, - 0x61, 0x74, 0x65, 0x4c, 0x6f, 0x67, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x56, 0x0a, 0x0e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x44, 0x45, 0x52, 0x50, 0x4d, 0x61, 0x70, - 0x73, 0x12, 0x27, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, - 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x44, 0x45, 0x52, 0x50, 0x4d, - 0x61, 0x70, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x63, 0x6f, 0x64, - 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x44, 0x45, - 0x52, 0x50, 0x4d, 0x61, 0x70, 0x30, 0x01, 0x12, 0x62, 0x0a, 0x11, 0x43, 0x6f, 0x6f, 0x72, 0x64, - 0x69, 0x6e, 0x61, 0x74, 0x65, 0x54, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x12, 0x23, 0x2e, 0x63, - 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, - 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x24, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, - 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x28, 0x01, 0x30, 0x01, 0x42, 0x27, 0x5a, 0x25, 0x67, - 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2f, - 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2f, 0x76, 0x32, 0x2f, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2f, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x74, 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x62, 0x0a, 0x0f, 0x42, 0x61, + 0x74, 0x63, 0x68, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4c, 0x6f, 0x67, 0x73, 0x12, 0x26, 0x2e, + 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x42, + 0x61, 0x74, 0x63, 0x68, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4c, 0x6f, 0x67, 0x73, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, + 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x43, 0x72, 0x65, 0x61, + 0x74, 0x65, 0x4c, 0x6f, 0x67, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x56, + 0x0a, 0x0e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x44, 0x45, 0x52, 0x50, 0x4d, 0x61, 0x70, 0x73, + 0x12, 0x27, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, + 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x44, 0x45, 0x52, 0x50, 0x4d, 0x61, + 0x70, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x63, 0x6f, 0x64, 0x65, + 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x44, 0x45, 0x52, + 0x50, 0x4d, 0x61, 0x70, 0x30, 0x01, 0x12, 0x62, 0x0a, 0x11, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x69, + 0x6e, 0x61, 0x74, 0x65, 0x54, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x12, 0x23, 0x2e, 0x63, 0x6f, + 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x43, + 0x6f, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x24, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, + 0x2e, 0x76, 0x32, 0x2e, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x28, 0x01, 0x30, 0x01, 0x42, 0x27, 0x5a, 0x25, 0x67, 0x69, + 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2f, 0x63, + 0x6f, 0x64, 0x65, 0x72, 0x2f, 0x76, 0x32, 0x2f, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2f, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -2059,93 +2645,112 @@ func file_agent_proto_agent_proto_rawDescGZIP() []byte { return file_agent_proto_agent_proto_rawDescData } -var file_agent_proto_agent_proto_enumTypes = make([]protoimpl.EnumInfo, 6) -var file_agent_proto_agent_proto_msgTypes = make([]protoimpl.MessageInfo, 25) +var file_agent_proto_agent_proto_enumTypes = make([]protoimpl.EnumInfo, 7) +var file_agent_proto_agent_proto_msgTypes = make([]protoimpl.MessageInfo, 30) var file_agent_proto_agent_proto_goTypes = []interface{}{ - (AppHealth)(0), // 0: coder.agent.v2.AppHealth - (WorkspaceApp_SharingLevel)(0), // 1: coder.agent.v2.WorkspaceApp.SharingLevel - (WorkspaceApp_Health)(0), // 2: coder.agent.v2.WorkspaceApp.Health - (Stats_Metric_Type)(0), // 3: coder.agent.v2.Stats.Metric.Type - (Lifecycle_State)(0), // 4: coder.agent.v2.Lifecycle.State - (Log_Level)(0), // 5: coder.agent.v2.Log.Level - (*WorkspaceApp)(nil), // 6: coder.agent.v2.WorkspaceApp - (*Manifest)(nil), // 7: coder.agent.v2.Manifest - (*GetManifestRequest)(nil), // 8: coder.agent.v2.GetManifestRequest - (*ServiceBanner)(nil), // 9: coder.agent.v2.ServiceBanner - (*GetServiceBannerRequest)(nil), // 10: coder.agent.v2.GetServiceBannerRequest - (*Stats)(nil), // 11: coder.agent.v2.Stats - (*UpdateStatsRequest)(nil), // 12: coder.agent.v2.UpdateStatsRequest - (*UpdateStatsResponse)(nil), // 13: coder.agent.v2.UpdateStatsResponse - (*Lifecycle)(nil), // 14: coder.agent.v2.Lifecycle - (*UpdateLifecycleRequest)(nil), // 15: coder.agent.v2.UpdateLifecycleRequest - (*BatchUpdateAppHealthRequest)(nil), // 16: coder.agent.v2.BatchUpdateAppHealthRequest - (*BatchUpdateAppHealthResponse)(nil), // 17: coder.agent.v2.BatchUpdateAppHealthResponse - (*Startup)(nil), // 18: coder.agent.v2.Startup - (*UpdateStartupRequest)(nil), // 19: coder.agent.v2.UpdateStartupRequest - (*Metadata)(nil), // 20: coder.agent.v2.Metadata - (*BatchUpdateMetadataRequest)(nil), // 21: coder.agent.v2.BatchUpdateMetadataRequest - (*BatchUpdateMetadataResponse)(nil), // 22: coder.agent.v2.BatchUpdateMetadataResponse - (*Log)(nil), // 23: coder.agent.v2.Log - (*BatchCreateLogsRequest)(nil), // 24: coder.agent.v2.BatchCreateLogsRequest - (*BatchCreateLogsResponse)(nil), // 25: coder.agent.v2.BatchCreateLogsResponse - (*WorkspaceApp_HealthCheck)(nil), // 26: coder.agent.v2.WorkspaceApp.HealthCheck - nil, // 27: coder.agent.v2.Stats.ConnectionsByProtoEntry - (*Stats_Metric)(nil), // 28: coder.agent.v2.Stats.Metric - nil, // 29: coder.agent.v2.Stats.Metric.LabelsEntry - (*BatchUpdateAppHealthRequest_HealthUpdate)(nil), // 30: coder.agent.v2.BatchUpdateAppHealthRequest.HealthUpdate - (*proto.DERPMap)(nil), // 31: coder.tailnet.v2.DERPMap - (*durationpb.Duration)(nil), // 32: google.protobuf.Duration - (*timestamppb.Timestamp)(nil), // 33: google.protobuf.Timestamp - (*proto.StreamDERPMapsRequest)(nil), // 34: coder.tailnet.v2.StreamDERPMapsRequest - (*proto.CoordinateRequest)(nil), // 35: coder.tailnet.v2.CoordinateRequest - (*proto.CoordinateResponse)(nil), // 36: coder.tailnet.v2.CoordinateResponse + (AppHealth)(0), // 0: coder.agent.v2.AppHealth + (WorkspaceApp_SharingLevel)(0), // 1: coder.agent.v2.WorkspaceApp.SharingLevel + (WorkspaceApp_Health)(0), // 2: coder.agent.v2.WorkspaceApp.Health + (Stats_Metric_Type)(0), // 3: coder.agent.v2.Stats.Metric.Type + (Lifecycle_State)(0), // 4: coder.agent.v2.Lifecycle.State + (Startup_Subsystem)(0), // 5: coder.agent.v2.Startup.Subsystem + (Log_Level)(0), // 6: coder.agent.v2.Log.Level + (*WorkspaceApp)(nil), // 7: coder.agent.v2.WorkspaceApp + (*WorkspaceAgentScript)(nil), // 8: coder.agent.v2.WorkspaceAgentScript + (*WorkspaceAgentMetadata)(nil), // 9: coder.agent.v2.WorkspaceAgentMetadata + (*Manifest)(nil), // 10: coder.agent.v2.Manifest + (*GetManifestRequest)(nil), // 11: coder.agent.v2.GetManifestRequest + (*ServiceBanner)(nil), // 12: coder.agent.v2.ServiceBanner + (*GetServiceBannerRequest)(nil), // 13: coder.agent.v2.GetServiceBannerRequest + (*Stats)(nil), // 14: coder.agent.v2.Stats + (*UpdateStatsRequest)(nil), // 15: coder.agent.v2.UpdateStatsRequest + (*UpdateStatsResponse)(nil), // 16: coder.agent.v2.UpdateStatsResponse + (*Lifecycle)(nil), // 17: coder.agent.v2.Lifecycle + (*UpdateLifecycleRequest)(nil), // 18: coder.agent.v2.UpdateLifecycleRequest + (*BatchUpdateAppHealthRequest)(nil), // 19: coder.agent.v2.BatchUpdateAppHealthRequest + (*BatchUpdateAppHealthResponse)(nil), // 20: coder.agent.v2.BatchUpdateAppHealthResponse + (*Startup)(nil), // 21: coder.agent.v2.Startup + (*UpdateStartupRequest)(nil), // 22: coder.agent.v2.UpdateStartupRequest + (*Metadata)(nil), // 23: coder.agent.v2.Metadata + (*BatchUpdateMetadataRequest)(nil), // 24: coder.agent.v2.BatchUpdateMetadataRequest + (*BatchUpdateMetadataResponse)(nil), // 25: coder.agent.v2.BatchUpdateMetadataResponse + (*Log)(nil), // 26: coder.agent.v2.Log + (*BatchCreateLogsRequest)(nil), // 27: coder.agent.v2.BatchCreateLogsRequest + (*BatchCreateLogsResponse)(nil), // 28: coder.agent.v2.BatchCreateLogsResponse + (*WorkspaceApp_Healthcheck)(nil), // 29: coder.agent.v2.WorkspaceApp.Healthcheck + (*WorkspaceAgentMetadata_Result)(nil), // 30: coder.agent.v2.WorkspaceAgentMetadata.Result + (*WorkspaceAgentMetadata_Description)(nil), // 31: coder.agent.v2.WorkspaceAgentMetadata.Description + nil, // 32: coder.agent.v2.Manifest.EnvironmentVariablesEntry + nil, // 33: coder.agent.v2.Stats.ConnectionsByProtoEntry + (*Stats_Metric)(nil), // 34: coder.agent.v2.Stats.Metric + (*Stats_Metric_Label)(nil), // 35: coder.agent.v2.Stats.Metric.Label + (*BatchUpdateAppHealthRequest_HealthUpdate)(nil), // 36: coder.agent.v2.BatchUpdateAppHealthRequest.HealthUpdate + (*durationpb.Duration)(nil), // 37: google.protobuf.Duration + (*proto.DERPMap)(nil), // 38: coder.tailnet.v2.DERPMap + (*timestamppb.Timestamp)(nil), // 39: google.protobuf.Timestamp + (*proto.StreamDERPMapsRequest)(nil), // 40: coder.tailnet.v2.StreamDERPMapsRequest + (*proto.CoordinateRequest)(nil), // 41: coder.tailnet.v2.CoordinateRequest + (*proto.CoordinateResponse)(nil), // 42: coder.tailnet.v2.CoordinateResponse } var file_agent_proto_agent_proto_depIdxs = []int32{ 1, // 0: coder.agent.v2.WorkspaceApp.sharing_level:type_name -> coder.agent.v2.WorkspaceApp.SharingLevel - 26, // 1: coder.agent.v2.WorkspaceApp.healthcheck:type_name -> coder.agent.v2.WorkspaceApp.HealthCheck + 29, // 1: coder.agent.v2.WorkspaceApp.healthcheck:type_name -> coder.agent.v2.WorkspaceApp.Healthcheck 2, // 2: coder.agent.v2.WorkspaceApp.health:type_name -> coder.agent.v2.WorkspaceApp.Health - 6, // 3: coder.agent.v2.Manifest.apps:type_name -> coder.agent.v2.WorkspaceApp - 31, // 4: coder.agent.v2.Manifest.derp_map:type_name -> coder.tailnet.v2.DERPMap - 27, // 5: coder.agent.v2.Stats.connections_by_proto:type_name -> coder.agent.v2.Stats.ConnectionsByProtoEntry - 11, // 6: coder.agent.v2.UpdateStatsRequest.stats:type_name -> coder.agent.v2.Stats - 32, // 7: coder.agent.v2.UpdateStatsResponse.report_interval_nanoseconds:type_name -> google.protobuf.Duration - 4, // 8: coder.agent.v2.Lifecycle.state:type_name -> coder.agent.v2.Lifecycle.State - 14, // 9: coder.agent.v2.UpdateLifecycleRequest.lifecycle:type_name -> coder.agent.v2.Lifecycle - 30, // 10: coder.agent.v2.BatchUpdateAppHealthRequest.updates:type_name -> coder.agent.v2.BatchUpdateAppHealthRequest.HealthUpdate - 18, // 11: coder.agent.v2.UpdateStartupRequest.startup:type_name -> coder.agent.v2.Startup - 33, // 12: coder.agent.v2.Metadata.collected_at:type_name -> google.protobuf.Timestamp - 20, // 13: coder.agent.v2.BatchUpdateMetadataRequest.metadata:type_name -> coder.agent.v2.Metadata - 33, // 14: coder.agent.v2.Log.created_at:type_name -> google.protobuf.Timestamp - 5, // 15: coder.agent.v2.Log.level:type_name -> coder.agent.v2.Log.Level - 23, // 16: coder.agent.v2.BatchCreateLogsRequest.logs:type_name -> coder.agent.v2.Log - 3, // 17: coder.agent.v2.Stats.Metric.type:type_name -> coder.agent.v2.Stats.Metric.Type - 29, // 18: coder.agent.v2.Stats.Metric.labels:type_name -> coder.agent.v2.Stats.Metric.LabelsEntry - 0, // 19: coder.agent.v2.BatchUpdateAppHealthRequest.HealthUpdate.health:type_name -> coder.agent.v2.AppHealth - 8, // 20: coder.agent.v2.Agent.GetManifest:input_type -> coder.agent.v2.GetManifestRequest - 10, // 21: coder.agent.v2.Agent.GetServiceBanner:input_type -> coder.agent.v2.GetServiceBannerRequest - 12, // 22: coder.agent.v2.Agent.UpdateStats:input_type -> coder.agent.v2.UpdateStatsRequest - 15, // 23: coder.agent.v2.Agent.UpdateLifecycle:input_type -> coder.agent.v2.UpdateLifecycleRequest - 16, // 24: coder.agent.v2.Agent.BatchUpdateAppHealths:input_type -> coder.agent.v2.BatchUpdateAppHealthRequest - 19, // 25: coder.agent.v2.Agent.UpdateStartup:input_type -> coder.agent.v2.UpdateStartupRequest - 21, // 26: coder.agent.v2.Agent.BatchUpdateMetadata:input_type -> coder.agent.v2.BatchUpdateMetadataRequest - 24, // 27: coder.agent.v2.Agent.BatchCreateLogs:input_type -> coder.agent.v2.BatchCreateLogsRequest - 34, // 28: coder.agent.v2.Agent.StreamDERPMaps:input_type -> coder.tailnet.v2.StreamDERPMapsRequest - 35, // 29: coder.agent.v2.Agent.CoordinateTailnet:input_type -> coder.tailnet.v2.CoordinateRequest - 7, // 30: coder.agent.v2.Agent.GetManifest:output_type -> coder.agent.v2.Manifest - 9, // 31: coder.agent.v2.Agent.GetServiceBanner:output_type -> coder.agent.v2.ServiceBanner - 13, // 32: coder.agent.v2.Agent.UpdateStats:output_type -> coder.agent.v2.UpdateStatsResponse - 14, // 33: coder.agent.v2.Agent.UpdateLifecycle:output_type -> coder.agent.v2.Lifecycle - 17, // 34: coder.agent.v2.Agent.BatchUpdateAppHealths:output_type -> coder.agent.v2.BatchUpdateAppHealthResponse - 18, // 35: coder.agent.v2.Agent.UpdateStartup:output_type -> coder.agent.v2.Startup - 22, // 36: coder.agent.v2.Agent.BatchUpdateMetadata:output_type -> coder.agent.v2.BatchUpdateMetadataResponse - 25, // 37: coder.agent.v2.Agent.BatchCreateLogs:output_type -> coder.agent.v2.BatchCreateLogsResponse - 31, // 38: coder.agent.v2.Agent.StreamDERPMaps:output_type -> coder.tailnet.v2.DERPMap - 36, // 39: coder.agent.v2.Agent.CoordinateTailnet:output_type -> coder.tailnet.v2.CoordinateResponse - 30, // [30:40] is the sub-list for method output_type - 20, // [20:30] is the sub-list for method input_type - 20, // [20:20] is the sub-list for extension type_name - 20, // [20:20] is the sub-list for extension extendee - 0, // [0:20] is the sub-list for field type_name + 37, // 3: coder.agent.v2.WorkspaceAgentScript.timeout:type_name -> google.protobuf.Duration + 30, // 4: coder.agent.v2.WorkspaceAgentMetadata.result:type_name -> coder.agent.v2.WorkspaceAgentMetadata.Result + 31, // 5: coder.agent.v2.WorkspaceAgentMetadata.description:type_name -> coder.agent.v2.WorkspaceAgentMetadata.Description + 32, // 6: coder.agent.v2.Manifest.environment_variables:type_name -> coder.agent.v2.Manifest.EnvironmentVariablesEntry + 38, // 7: coder.agent.v2.Manifest.derp_map:type_name -> coder.tailnet.v2.DERPMap + 8, // 8: coder.agent.v2.Manifest.scripts:type_name -> coder.agent.v2.WorkspaceAgentScript + 7, // 9: coder.agent.v2.Manifest.apps:type_name -> coder.agent.v2.WorkspaceApp + 31, // 10: coder.agent.v2.Manifest.metadata:type_name -> coder.agent.v2.WorkspaceAgentMetadata.Description + 33, // 11: coder.agent.v2.Stats.connections_by_proto:type_name -> coder.agent.v2.Stats.ConnectionsByProtoEntry + 34, // 12: coder.agent.v2.Stats.metrics:type_name -> coder.agent.v2.Stats.Metric + 14, // 13: coder.agent.v2.UpdateStatsRequest.stats:type_name -> coder.agent.v2.Stats + 37, // 14: coder.agent.v2.UpdateStatsResponse.report_interval:type_name -> google.protobuf.Duration + 4, // 15: coder.agent.v2.Lifecycle.state:type_name -> coder.agent.v2.Lifecycle.State + 39, // 16: coder.agent.v2.Lifecycle.changed_at:type_name -> google.protobuf.Timestamp + 17, // 17: coder.agent.v2.UpdateLifecycleRequest.lifecycle:type_name -> coder.agent.v2.Lifecycle + 36, // 18: coder.agent.v2.BatchUpdateAppHealthRequest.updates:type_name -> coder.agent.v2.BatchUpdateAppHealthRequest.HealthUpdate + 5, // 19: coder.agent.v2.Startup.subsystems:type_name -> coder.agent.v2.Startup.Subsystem + 21, // 20: coder.agent.v2.UpdateStartupRequest.startup:type_name -> coder.agent.v2.Startup + 30, // 21: coder.agent.v2.Metadata.result:type_name -> coder.agent.v2.WorkspaceAgentMetadata.Result + 23, // 22: coder.agent.v2.BatchUpdateMetadataRequest.metadata:type_name -> coder.agent.v2.Metadata + 39, // 23: coder.agent.v2.Log.created_at:type_name -> google.protobuf.Timestamp + 6, // 24: coder.agent.v2.Log.level:type_name -> coder.agent.v2.Log.Level + 26, // 25: coder.agent.v2.BatchCreateLogsRequest.logs:type_name -> coder.agent.v2.Log + 37, // 26: coder.agent.v2.WorkspaceApp.Healthcheck.interval:type_name -> google.protobuf.Duration + 39, // 27: coder.agent.v2.WorkspaceAgentMetadata.Result.collected_at:type_name -> google.protobuf.Timestamp + 37, // 28: coder.agent.v2.WorkspaceAgentMetadata.Description.interval:type_name -> google.protobuf.Duration + 37, // 29: coder.agent.v2.WorkspaceAgentMetadata.Description.timeout:type_name -> google.protobuf.Duration + 3, // 30: coder.agent.v2.Stats.Metric.type:type_name -> coder.agent.v2.Stats.Metric.Type + 35, // 31: coder.agent.v2.Stats.Metric.labels:type_name -> coder.agent.v2.Stats.Metric.Label + 0, // 32: coder.agent.v2.BatchUpdateAppHealthRequest.HealthUpdate.health:type_name -> coder.agent.v2.AppHealth + 11, // 33: coder.agent.v2.Agent.GetManifest:input_type -> coder.agent.v2.GetManifestRequest + 13, // 34: coder.agent.v2.Agent.GetServiceBanner:input_type -> coder.agent.v2.GetServiceBannerRequest + 15, // 35: coder.agent.v2.Agent.UpdateStats:input_type -> coder.agent.v2.UpdateStatsRequest + 18, // 36: coder.agent.v2.Agent.UpdateLifecycle:input_type -> coder.agent.v2.UpdateLifecycleRequest + 19, // 37: coder.agent.v2.Agent.BatchUpdateAppHealths:input_type -> coder.agent.v2.BatchUpdateAppHealthRequest + 22, // 38: coder.agent.v2.Agent.UpdateStartup:input_type -> coder.agent.v2.UpdateStartupRequest + 24, // 39: coder.agent.v2.Agent.BatchUpdateMetadata:input_type -> coder.agent.v2.BatchUpdateMetadataRequest + 27, // 40: coder.agent.v2.Agent.BatchCreateLogs:input_type -> coder.agent.v2.BatchCreateLogsRequest + 40, // 41: coder.agent.v2.Agent.StreamDERPMaps:input_type -> coder.tailnet.v2.StreamDERPMapsRequest + 41, // 42: coder.agent.v2.Agent.CoordinateTailnet:input_type -> coder.tailnet.v2.CoordinateRequest + 10, // 43: coder.agent.v2.Agent.GetManifest:output_type -> coder.agent.v2.Manifest + 12, // 44: coder.agent.v2.Agent.GetServiceBanner:output_type -> coder.agent.v2.ServiceBanner + 16, // 45: coder.agent.v2.Agent.UpdateStats:output_type -> coder.agent.v2.UpdateStatsResponse + 17, // 46: coder.agent.v2.Agent.UpdateLifecycle:output_type -> coder.agent.v2.Lifecycle + 20, // 47: coder.agent.v2.Agent.BatchUpdateAppHealths:output_type -> coder.agent.v2.BatchUpdateAppHealthResponse + 21, // 48: coder.agent.v2.Agent.UpdateStartup:output_type -> coder.agent.v2.Startup + 25, // 49: coder.agent.v2.Agent.BatchUpdateMetadata:output_type -> coder.agent.v2.BatchUpdateMetadataResponse + 28, // 50: coder.agent.v2.Agent.BatchCreateLogs:output_type -> coder.agent.v2.BatchCreateLogsResponse + 38, // 51: coder.agent.v2.Agent.StreamDERPMaps:output_type -> coder.tailnet.v2.DERPMap + 42, // 52: coder.agent.v2.Agent.CoordinateTailnet:output_type -> coder.tailnet.v2.CoordinateResponse + 43, // [43:53] is the sub-list for method output_type + 33, // [33:43] is the sub-list for method input_type + 33, // [33:33] is the sub-list for extension type_name + 33, // [33:33] is the sub-list for extension extendee + 0, // [0:33] is the sub-list for field type_name } func init() { file_agent_proto_agent_proto_init() } @@ -2167,7 +2772,7 @@ func file_agent_proto_agent_proto_init() { } } file_agent_proto_agent_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Manifest); i { + switch v := v.(*WorkspaceAgentScript); i { case 0: return &v.state case 1: @@ -2179,7 +2784,7 @@ func file_agent_proto_agent_proto_init() { } } file_agent_proto_agent_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetManifestRequest); i { + switch v := v.(*WorkspaceAgentMetadata); i { case 0: return &v.state case 1: @@ -2191,7 +2796,7 @@ func file_agent_proto_agent_proto_init() { } } file_agent_proto_agent_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ServiceBanner); i { + switch v := v.(*Manifest); i { case 0: return &v.state case 1: @@ -2203,7 +2808,7 @@ func file_agent_proto_agent_proto_init() { } } file_agent_proto_agent_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetServiceBannerRequest); i { + switch v := v.(*GetManifestRequest); i { case 0: return &v.state case 1: @@ -2215,7 +2820,7 @@ func file_agent_proto_agent_proto_init() { } } file_agent_proto_agent_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Stats); i { + switch v := v.(*ServiceBanner); i { case 0: return &v.state case 1: @@ -2227,7 +2832,7 @@ func file_agent_proto_agent_proto_init() { } } file_agent_proto_agent_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*UpdateStatsRequest); i { + switch v := v.(*GetServiceBannerRequest); i { case 0: return &v.state case 1: @@ -2239,7 +2844,7 @@ func file_agent_proto_agent_proto_init() { } } file_agent_proto_agent_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*UpdateStatsResponse); i { + switch v := v.(*Stats); i { case 0: return &v.state case 1: @@ -2251,7 +2856,7 @@ func file_agent_proto_agent_proto_init() { } } file_agent_proto_agent_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Lifecycle); i { + switch v := v.(*UpdateStatsRequest); i { case 0: return &v.state case 1: @@ -2263,7 +2868,7 @@ func file_agent_proto_agent_proto_init() { } } file_agent_proto_agent_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*UpdateLifecycleRequest); i { + switch v := v.(*UpdateStatsResponse); i { case 0: return &v.state case 1: @@ -2275,7 +2880,7 @@ func file_agent_proto_agent_proto_init() { } } file_agent_proto_agent_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*BatchUpdateAppHealthRequest); i { + switch v := v.(*Lifecycle); i { case 0: return &v.state case 1: @@ -2287,7 +2892,7 @@ func file_agent_proto_agent_proto_init() { } } file_agent_proto_agent_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*BatchUpdateAppHealthResponse); i { + switch v := v.(*UpdateLifecycleRequest); i { case 0: return &v.state case 1: @@ -2299,7 +2904,7 @@ func file_agent_proto_agent_proto_init() { } } file_agent_proto_agent_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Startup); i { + switch v := v.(*BatchUpdateAppHealthRequest); i { case 0: return &v.state case 1: @@ -2311,7 +2916,7 @@ func file_agent_proto_agent_proto_init() { } } file_agent_proto_agent_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*UpdateStartupRequest); i { + switch v := v.(*BatchUpdateAppHealthResponse); i { case 0: return &v.state case 1: @@ -2323,7 +2928,7 @@ func file_agent_proto_agent_proto_init() { } } file_agent_proto_agent_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Metadata); i { + switch v := v.(*Startup); i { case 0: return &v.state case 1: @@ -2335,7 +2940,7 @@ func file_agent_proto_agent_proto_init() { } } file_agent_proto_agent_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*BatchUpdateMetadataRequest); i { + switch v := v.(*UpdateStartupRequest); i { case 0: return &v.state case 1: @@ -2347,7 +2952,7 @@ func file_agent_proto_agent_proto_init() { } } file_agent_proto_agent_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*BatchUpdateMetadataResponse); i { + switch v := v.(*Metadata); i { case 0: return &v.state case 1: @@ -2359,7 +2964,7 @@ func file_agent_proto_agent_proto_init() { } } file_agent_proto_agent_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Log); i { + switch v := v.(*BatchUpdateMetadataRequest); i { case 0: return &v.state case 1: @@ -2371,7 +2976,7 @@ func file_agent_proto_agent_proto_init() { } } file_agent_proto_agent_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*BatchCreateLogsRequest); i { + switch v := v.(*BatchUpdateMetadataResponse); i { case 0: return &v.state case 1: @@ -2383,7 +2988,7 @@ func file_agent_proto_agent_proto_init() { } } file_agent_proto_agent_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*BatchCreateLogsResponse); i { + switch v := v.(*Log); i { case 0: return &v.state case 1: @@ -2395,7 +3000,19 @@ func file_agent_proto_agent_proto_init() { } } file_agent_proto_agent_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*WorkspaceApp_HealthCheck); i { + switch v := v.(*BatchCreateLogsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_agent_proto_agent_proto_msgTypes[21].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*BatchCreateLogsResponse); i { case 0: return &v.state case 1: @@ -2407,7 +3024,19 @@ func file_agent_proto_agent_proto_init() { } } file_agent_proto_agent_proto_msgTypes[22].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Stats_Metric); i { + switch v := v.(*WorkspaceApp_Healthcheck); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_agent_proto_agent_proto_msgTypes[23].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*WorkspaceAgentMetadata_Result); i { case 0: return &v.state case 1: @@ -2419,6 +3048,42 @@ func file_agent_proto_agent_proto_init() { } } file_agent_proto_agent_proto_msgTypes[24].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*WorkspaceAgentMetadata_Description); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_agent_proto_agent_proto_msgTypes[27].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Stats_Metric); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_agent_proto_agent_proto_msgTypes[28].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Stats_Metric_Label); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_agent_proto_agent_proto_msgTypes[29].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*BatchUpdateAppHealthRequest_HealthUpdate); i { case 0: return &v.state @@ -2436,8 +3101,8 @@ func file_agent_proto_agent_proto_init() { File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_agent_proto_agent_proto_rawDesc, - NumEnums: 6, - NumMessages: 25, + NumEnums: 7, + NumMessages: 30, NumExtensions: 0, NumServices: 1, }, diff --git a/agent/proto/agent.proto b/agent/proto/agent.proto index 5d6c09c167..f806efed73 100644 --- a/agent/proto/agent.proto +++ b/agent/proto/agent.proto @@ -8,7 +8,7 @@ import "google/protobuf/timestamp.proto"; import "google/protobuf/duration.proto"; message WorkspaceApp { - bytes uuid = 1; + bytes id = 1; string url = 2; bool external = 3; string slug = 4; @@ -26,12 +26,12 @@ message WorkspaceApp { } SharingLevel sharing_level = 10; - message HealthCheck { + message Healthcheck { string url = 1; - int32 interval = 2; + google.protobuf.Duration interval = 2; int32 threshold = 3; } - HealthCheck healthcheck = 11; + Healthcheck healthcheck = 11; enum Health { HEALTH_UNSPECIFIED = 0; @@ -43,11 +43,52 @@ message WorkspaceApp { Health health = 12; } +message WorkspaceAgentScript { + bytes log_source_id = 1; + string log_path = 2; + string script = 3; + string cron = 4; + bool run_on_start = 5; + bool run_on_stop = 6; + bool start_blocks_login = 7; + google.protobuf.Duration timeout = 8; +} + +message WorkspaceAgentMetadata { + message Result { + google.protobuf.Timestamp collected_at = 1; + int64 age = 2; + string value = 3; + string error = 4; + } + Result result = 1; + + message Description { + string display_name = 1; + string key = 2; + string script = 3; + google.protobuf.Duration interval = 4; + google.protobuf.Duration timeout = 5; + } + Description description = 2; +} + message Manifest { - uint32 git_auth_configs = 1; - string vs_code_port_proxy_uri = 2; - repeated WorkspaceApp apps = 3; - coder.tailnet.v2.DERPMap derp_map = 4; + bytes agent_id = 1; + string owner_username = 13; + bytes workspace_id = 14; + uint32 git_auth_configs = 2; + map environment_variables = 3; + string directory = 4; + string vs_code_port_proxy_uri = 5; + string motd_path = 6; + bool disable_direct_connections = 7; + bool derp_force_websockets = 8; + + coder.tailnet.v2.DERPMap derp_map = 9; + repeated WorkspaceAgentScript scripts = 10; + repeated WorkspaceApp apps = 11; + repeated WorkspaceAgentMetadata.Description metadata = 12; } message GetManifestRequest {} @@ -100,8 +141,14 @@ message Stats { Type type = 2; double value = 3; - map labels = 4; + + message Label { + string name = 1; + string value = 2; + } + repeated Label labels = 4; } + repeated Metric metrics = 12; } message UpdateStatsRequest{ @@ -109,14 +156,14 @@ message UpdateStatsRequest{ } message UpdateStatsResponse { - google.protobuf.Duration report_interval_nanoseconds = 1; + google.protobuf.Duration report_interval = 1; } message Lifecycle { enum State { STATE_UNSPECIFIED = 0; CREATED = 1; - STARTED = 2; + STARTING = 2; START_TIMEOUT = 3; START_ERROR = 4; READY = 5; @@ -126,6 +173,7 @@ message Lifecycle { OFF = 9; } State state = 1; + google.protobuf.Timestamp changed_at = 2; } message UpdateLifecycleRequest { @@ -142,7 +190,7 @@ enum AppHealth { message BatchUpdateAppHealthRequest { message HealthUpdate { - bytes uuid = 1; + bytes id = 1; AppHealth health = 2; } repeated HealthUpdate updates = 1; @@ -153,7 +201,13 @@ message BatchUpdateAppHealthResponse {} message Startup { string version = 1; string expanded_directory = 2; - repeated string subsystems = 3; + enum Subsystem { + SUBSYSTEM_UNSPECIFIED = 0; + ENVBOX = 1; + ENVBUILDER = 2; + EXECTRACE = 3; + } + repeated Subsystem subsystems = 3; } message UpdateStartupRequest{ @@ -162,10 +216,7 @@ message UpdateStartupRequest{ message Metadata { string key = 1; - google.protobuf.Timestamp collected_at = 2; - int64 age = 3; - string value = 4; - string error = 5; + WorkspaceAgentMetadata.Result result = 2; } message BatchUpdateMetadataRequest { @@ -190,7 +241,7 @@ message Log { } message BatchCreateLogsRequest { - bytes source_id = 1; + bytes log_source_id = 1; repeated Log logs = 2; } diff --git a/agent/proto/convert.go b/agent/proto/convert.go new file mode 100644 index 0000000000..b0d373913f --- /dev/null +++ b/agent/proto/convert.go @@ -0,0 +1,192 @@ +package proto + +import ( + "strings" + "time" + + "github.com/google/uuid" + "golang.org/x/xerrors" + "google.golang.org/protobuf/types/known/durationpb" + + "github.com/coder/coder/v2/coderd/database" + "github.com/coder/coder/v2/coderd/database/db2sdk" + "github.com/coder/coder/v2/codersdk" +) + +func DBAgentMetadataToProtoDescription(metadata []database.WorkspaceAgentMetadatum) []*WorkspaceAgentMetadata_Description { + ret := make([]*WorkspaceAgentMetadata_Description, len(metadata)) + for i, metadatum := range metadata { + ret[i] = DBAgentMetadatumToProtoDescription(metadatum) + } + return ret +} + +func DBAgentMetadatumToProtoDescription(metadatum database.WorkspaceAgentMetadatum) *WorkspaceAgentMetadata_Description { + return &WorkspaceAgentMetadata_Description{ + DisplayName: metadatum.DisplayName, + Key: metadatum.Key, + Script: metadatum.Script, + Interval: durationpb.New(time.Duration(metadatum.Interval)), + Timeout: durationpb.New(time.Duration(metadatum.Timeout)), + } +} + +func SDKAgentMetadataDescriptionsFromProto(descriptions []*WorkspaceAgentMetadata_Description) []codersdk.WorkspaceAgentMetadataDescription { + ret := make([]codersdk.WorkspaceAgentMetadataDescription, len(descriptions)) + for i, description := range descriptions { + ret[i] = SDKAgentMetadataDescriptionFromProto(description) + } + return ret +} + +func SDKAgentMetadataDescriptionFromProto(description *WorkspaceAgentMetadata_Description) codersdk.WorkspaceAgentMetadataDescription { + return codersdk.WorkspaceAgentMetadataDescription{ + DisplayName: description.DisplayName, + Key: description.Key, + Script: description.Script, + Interval: int64(description.Interval.AsDuration()), + Timeout: int64(description.Timeout.AsDuration()), + } +} + +func DBAgentScriptsToProto(scripts []database.WorkspaceAgentScript) []*WorkspaceAgentScript { + ret := make([]*WorkspaceAgentScript, len(scripts)) + for i, script := range scripts { + ret[i] = DBAgentScriptToProto(script) + } + return ret +} + +func DBAgentScriptToProto(script database.WorkspaceAgentScript) *WorkspaceAgentScript { + return &WorkspaceAgentScript{ + LogSourceId: script.LogSourceID[:], + LogPath: script.LogPath, + Script: script.Script, + Cron: script.Cron, + RunOnStart: script.RunOnStart, + RunOnStop: script.RunOnStop, + StartBlocksLogin: script.StartBlocksLogin, + Timeout: durationpb.New(time.Duration(script.TimeoutSeconds) * time.Second), + } +} + +func SDKAgentScriptsFromProto(protoScripts []*WorkspaceAgentScript) ([]codersdk.WorkspaceAgentScript, error) { + ret := make([]codersdk.WorkspaceAgentScript, len(protoScripts)) + for i, protoScript := range protoScripts { + app, err := SDKAgentScriptFromProto(protoScript) + if err != nil { + return nil, xerrors.Errorf("parse script %v: %w", i, err) + } + ret[i] = app + } + return ret, nil +} + +func SDKAgentScriptFromProto(protoScript *WorkspaceAgentScript) (codersdk.WorkspaceAgentScript, error) { + id, err := uuid.FromBytes(protoScript.LogSourceId) + if err != nil { + return codersdk.WorkspaceAgentScript{}, xerrors.Errorf("parse id: %w", err) + } + + return codersdk.WorkspaceAgentScript{ + LogSourceID: id, + LogPath: protoScript.LogPath, + Script: protoScript.Script, + Cron: protoScript.Cron, + RunOnStart: protoScript.RunOnStart, + RunOnStop: protoScript.RunOnStop, + StartBlocksLogin: protoScript.StartBlocksLogin, + Timeout: protoScript.Timeout.AsDuration(), + }, nil +} + +func DBAppsToProto(dbApps []database.WorkspaceApp, agent database.WorkspaceAgent, ownerName string, workspace database.Workspace) ([]*WorkspaceApp, error) { + ret := make([]*WorkspaceApp, len(dbApps)) + for i, dbApp := range dbApps { + var err error + ret[i], err = DBAppToProto(dbApp, agent, ownerName, workspace) + if err != nil { + return nil, xerrors.Errorf("parse app %v (%q): %w", i, dbApp.Slug, err) + } + } + return ret, nil +} + +func DBAppToProto(dbApp database.WorkspaceApp, agent database.WorkspaceAgent, ownerName string, workspace database.Workspace) (*WorkspaceApp, error) { + sharingLevelRaw, ok := WorkspaceApp_SharingLevel_value[strings.ToUpper(string(dbApp.SharingLevel))] + if !ok { + return nil, xerrors.Errorf("unknown app sharing level: %q", dbApp.SharingLevel) + } + + healthRaw, ok := WorkspaceApp_Health_value[strings.ToUpper(string(dbApp.Health))] + if !ok { + return nil, xerrors.Errorf("unknown app health: %q", dbApp.SharingLevel) + } + + return &WorkspaceApp{ + Id: dbApp.ID[:], + Url: dbApp.Url.String, + External: dbApp.External, + Slug: dbApp.Slug, + DisplayName: dbApp.DisplayName, + Command: dbApp.Command.String, + Icon: dbApp.Icon, + Subdomain: dbApp.Subdomain, + SubdomainName: db2sdk.AppSubdomain(dbApp, agent.Name, workspace.Name, ownerName), + SharingLevel: WorkspaceApp_SharingLevel(sharingLevelRaw), + Healthcheck: &WorkspaceApp_Healthcheck{ + Url: dbApp.HealthcheckUrl, + Interval: durationpb.New(time.Duration(dbApp.HealthcheckInterval) * time.Second), + Threshold: dbApp.HealthcheckThreshold, + }, + Health: WorkspaceApp_Health(healthRaw), + }, nil +} + +func SDKAppsFromProto(protoApps []*WorkspaceApp) ([]codersdk.WorkspaceApp, error) { + ret := make([]codersdk.WorkspaceApp, len(protoApps)) + for i, protoApp := range protoApps { + app, err := SDKAppFromProto(protoApp) + if err != nil { + return nil, xerrors.Errorf("parse app %v (%q): %w", i, protoApp.Slug, err) + } + ret[i] = app + } + return ret, nil +} + +func SDKAppFromProto(protoApp *WorkspaceApp) (codersdk.WorkspaceApp, error) { + id, err := uuid.FromBytes(protoApp.Id) + if err != nil { + return codersdk.WorkspaceApp{}, xerrors.Errorf("parse id: %w", err) + } + + var sharingLevel codersdk.WorkspaceAppSharingLevel = codersdk.WorkspaceAppSharingLevel(strings.ToLower(protoApp.SharingLevel.String())) + if _, ok := codersdk.MapWorkspaceAppSharingLevels[sharingLevel]; !ok { + return codersdk.WorkspaceApp{}, xerrors.Errorf("unknown app sharing level: %v (%q)", protoApp.SharingLevel, protoApp.SharingLevel.String()) + } + + var health codersdk.WorkspaceAppHealth = codersdk.WorkspaceAppHealth(strings.ToLower(protoApp.Health.String())) + if _, ok := codersdk.MapWorkspaceAppHealths[health]; !ok { + return codersdk.WorkspaceApp{}, xerrors.Errorf("unknown app health: %v (%q)", protoApp.Health, protoApp.Health.String()) + } + + return codersdk.WorkspaceApp{ + ID: id, + URL: protoApp.Url, + External: protoApp.External, + Slug: protoApp.Slug, + DisplayName: protoApp.DisplayName, + Command: protoApp.Command, + Icon: protoApp.Icon, + Subdomain: protoApp.Subdomain, + SubdomainName: protoApp.SubdomainName, + SharingLevel: sharingLevel, + Healthcheck: codersdk.Healthcheck{ + URL: protoApp.Healthcheck.Url, + Interval: int32(protoApp.Healthcheck.Interval.AsDuration().Seconds()), + Threshold: protoApp.Healthcheck.Threshold, + }, + Health: health, + }, nil +} diff --git a/coderd/activitybump.go b/coderd/agentapi/activitybump.go similarity index 94% rename from coderd/activitybump.go rename to coderd/agentapi/activitybump.go index 89474bad7f..ab0797d612 100644 --- a/coderd/activitybump.go +++ b/coderd/agentapi/activitybump.go @@ -1,4 +1,4 @@ -package coderd +package agentapi import ( "context" @@ -11,7 +11,7 @@ import ( "github.com/coder/coder/v2/coderd/database" ) -// activityBumpWorkspace automatically bumps the workspace's auto-off timer +// ActivityBumpWorkspace automatically bumps the workspace's auto-off timer // if it is set to expire soon. The deadline will be bumped by 1 hour*. // If the bump crosses over an autostart time, the workspace will be // bumped by the workspace ttl instead. @@ -36,7 +36,7 @@ import ( // A way to avoid this is to configure the max deadline to something that will not // span more than 1 day. This will force the workspace to restart and reset the deadline // each morning when it autostarts. -func activityBumpWorkspace(ctx context.Context, log slog.Logger, db database.Store, workspaceID uuid.UUID, nextAutostart time.Time) { +func ActivityBumpWorkspace(ctx context.Context, log slog.Logger, db database.Store, workspaceID uuid.UUID, nextAutostart time.Time) { // We set a short timeout so if the app is under load, these // low priority operations fail first. ctx, cancel := context.WithTimeout(ctx, time.Second*15) diff --git a/coderd/activitybump_internal_test.go b/coderd/agentapi/activitybump_test.go similarity index 98% rename from coderd/activitybump_internal_test.go rename to coderd/agentapi/activitybump_test.go index 7975b1a18b..ceadb3e331 100644 --- a/coderd/activitybump_internal_test.go +++ b/coderd/agentapi/activitybump_test.go @@ -1,4 +1,4 @@ -package coderd +package agentapi_test import ( "database/sql" @@ -8,6 +8,7 @@ import ( "github.com/google/uuid" "cdr.dev/slog/sloggers/slogtest" + "github.com/coder/coder/v2/coderd/agentapi" "github.com/coder/coder/v2/coderd/database" "github.com/coder/coder/v2/coderd/database/dbgen" "github.com/coder/coder/v2/coderd/database/dbtestutil" @@ -236,7 +237,7 @@ func Test_ActivityBumpWorkspace(t *testing.T) { // Bump duration is measured from the time of the bump, so we measure from here. start := dbtime.Now() - activityBumpWorkspace(ctx, log, db, bld.WorkspaceID, tt.nextAutostart) + agentapi.ActivityBumpWorkspace(ctx, log, db, bld.WorkspaceID, tt.nextAutostart) end := dbtime.Now() // Validate our state after bump diff --git a/coderd/agentapi/api.go b/coderd/agentapi/api.go new file mode 100644 index 0000000000..57cb859aaf --- /dev/null +++ b/coderd/agentapi/api.go @@ -0,0 +1,234 @@ +package agentapi + +import ( + "context" + "io" + "net" + "net/url" + "sync" + "sync/atomic" + "time" + + "github.com/google/uuid" + "golang.org/x/xerrors" + "storj.io/drpc/drpcmux" + "storj.io/drpc/drpcserver" + "tailscale.com/tailcfg" + + "cdr.dev/slog" + agentproto "github.com/coder/coder/v2/agent/proto" + "github.com/coder/coder/v2/coderd/batchstats" + "github.com/coder/coder/v2/coderd/database" + "github.com/coder/coder/v2/coderd/database/pubsub" + "github.com/coder/coder/v2/coderd/externalauth" + "github.com/coder/coder/v2/coderd/prometheusmetrics" + "github.com/coder/coder/v2/coderd/schedule" + "github.com/coder/coder/v2/coderd/tracing" + "github.com/coder/coder/v2/codersdk/agentsdk" + "github.com/coder/coder/v2/tailnet" +) + +const AgentAPIVersionDRPC = "2.0" + +// API implements the DRPC agent API interface from agent/proto. This struct is +// instantiated once per agent connection and kept alive for the duration of the +// session. +type API struct { + opts Options + *ManifestAPI + *ServiceBannerAPI + *StatsAPI + *LifecycleAPI + *AppsAPI + *MetadataAPI + *LogsAPI + *TailnetAPI + + mu sync.Mutex + cachedWorkspaceID uuid.UUID +} + +var _ agentproto.DRPCAgentServer = &API{} + +type Options struct { + AgentID uuid.UUID + + Ctx context.Context + Log slog.Logger + Database database.Store + Pubsub pubsub.Pubsub + DerpMapFn func() *tailcfg.DERPMap + TailnetCoordinator *atomic.Pointer[tailnet.Coordinator] + TemplateScheduleStore *atomic.Pointer[schedule.TemplateScheduleStore] + StatsBatcher *batchstats.Batcher + PublishWorkspaceUpdateFn func(ctx context.Context, workspaceID uuid.UUID) + PublishWorkspaceAgentLogsUpdateFn func(ctx context.Context, workspaceAgentID uuid.UUID, msg agentsdk.LogsNotifyMessage) + + AccessURL *url.URL + AppHostname string + AgentInactiveDisconnectTimeout time.Duration + AgentFallbackTroubleshootingURL string + AgentStatsRefreshInterval time.Duration + DisableDirectConnections bool + DerpForceWebSockets bool + DerpMapUpdateFrequency time.Duration + ExternalAuthConfigs []*externalauth.Config + + // Optional: + // WorkspaceID avoids a future lookup to find the workspace ID by setting + // the cache in advance. + WorkspaceID uuid.UUID + UpdateAgentMetricsFn func(ctx context.Context, labels prometheusmetrics.AgentMetricLabels, metrics []*agentproto.Stats_Metric) +} + +func New(opts Options) *API { + api := &API{ + opts: opts, + mu: sync.Mutex{}, + cachedWorkspaceID: opts.WorkspaceID, + } + + api.ManifestAPI = &ManifestAPI{ + AccessURL: opts.AccessURL, + AppHostname: opts.AppHostname, + AgentInactiveDisconnectTimeout: opts.AgentInactiveDisconnectTimeout, + AgentFallbackTroubleshootingURL: opts.AgentFallbackTroubleshootingURL, + ExternalAuthConfigs: opts.ExternalAuthConfigs, + DisableDirectConnections: opts.DisableDirectConnections, + DerpForceWebSockets: opts.DerpForceWebSockets, + AgentFn: api.agent, + Database: opts.Database, + DerpMapFn: opts.DerpMapFn, + TailnetCoordinator: opts.TailnetCoordinator, + } + + api.ServiceBannerAPI = &ServiceBannerAPI{ + Database: opts.Database, + } + + api.StatsAPI = &StatsAPI{ + AgentFn: api.agent, + Database: opts.Database, + Log: opts.Log, + StatsBatcher: opts.StatsBatcher, + TemplateScheduleStore: opts.TemplateScheduleStore, + AgentStatsRefreshInterval: opts.AgentStatsRefreshInterval, + UpdateAgentMetricsFn: opts.UpdateAgentMetricsFn, + } + + api.LifecycleAPI = &LifecycleAPI{ + AgentFn: api.agent, + WorkspaceIDFn: api.workspaceID, + Database: opts.Database, + Log: opts.Log, + PublishWorkspaceUpdateFn: api.publishWorkspaceUpdate, + } + + api.AppsAPI = &AppsAPI{ + AgentFn: api.agent, + Database: opts.Database, + Log: opts.Log, + PublishWorkspaceUpdateFn: api.publishWorkspaceUpdate, + } + + api.MetadataAPI = &MetadataAPI{ + AgentFn: api.agent, + Database: opts.Database, + Pubsub: opts.Pubsub, + Log: opts.Log, + } + + api.LogsAPI = &LogsAPI{ + AgentFn: api.agent, + Database: opts.Database, + Log: opts.Log, + PublishWorkspaceUpdateFn: api.publishWorkspaceUpdate, + PublishWorkspaceAgentLogsUpdateFn: opts.PublishWorkspaceAgentLogsUpdateFn, + } + + api.TailnetAPI = &TailnetAPI{ + Ctx: opts.Ctx, + DerpMapFn: opts.DerpMapFn, + DerpMapUpdateFrequency: opts.DerpMapUpdateFrequency, + } + + return api +} + +func (a *API) Server(ctx context.Context) (*drpcserver.Server, error) { + mux := drpcmux.New() + err := agentproto.DRPCRegisterAgent(mux, a) + if err != nil { + return nil, xerrors.Errorf("register agent API protocol in DRPC mux: %w", err) + } + + return drpcserver.NewWithOptions(&tracing.DRPCHandler{Handler: mux}, + drpcserver.Options{ + Log: func(err error) { + if xerrors.Is(err, io.EOF) { + return + } + a.opts.Log.Debug(ctx, "drpc server error", slog.Error(err)) + }, + }, + ), nil +} + +func (a *API) Serve(ctx context.Context, l net.Listener) error { + server, err := a.Server(ctx) + if err != nil { + return xerrors.Errorf("create agent API server: %w", err) + } + + return server.Serve(ctx, l) +} + +func (a *API) agent(ctx context.Context) (database.WorkspaceAgent, error) { + agent, err := a.opts.Database.GetWorkspaceAgentByID(ctx, a.opts.AgentID) + if err != nil { + return database.WorkspaceAgent{}, xerrors.Errorf("get workspace agent by id %q: %w", a.opts.AgentID, err) + } + return agent, nil +} + +func (a *API) workspaceID(ctx context.Context, agent *database.WorkspaceAgent) (uuid.UUID, error) { + a.mu.Lock() + if a.cachedWorkspaceID != uuid.Nil { + id := a.cachedWorkspaceID + a.mu.Unlock() + return id, nil + } + + if agent == nil { + agnt, err := a.agent(ctx) + if err != nil { + return uuid.Nil, err + } + agent = &agnt + } + + resource, err := a.opts.Database.GetWorkspaceResourceByID(ctx, agent.ResourceID) + if err != nil { + return uuid.Nil, xerrors.Errorf("get workspace agent resource by id %q: %w", agent.ResourceID, err) + } + + build, err := a.opts.Database.GetWorkspaceBuildByJobID(ctx, resource.JobID) + if err != nil { + return uuid.Nil, xerrors.Errorf("get workspace build by job id %q: %w", resource.JobID, err) + } + + a.mu.Lock() + a.cachedWorkspaceID = build.WorkspaceID + a.mu.Unlock() + return build.WorkspaceID, nil +} + +func (a *API) publishWorkspaceUpdate(ctx context.Context, agent *database.WorkspaceAgent) error { + workspaceID, err := a.workspaceID(ctx, agent) + if err != nil { + return err + } + + a.opts.PublishWorkspaceUpdateFn(ctx, workspaceID) + return nil +} diff --git a/coderd/agentapi/apps.go b/coderd/agentapi/apps.go new file mode 100644 index 0000000000..1346d7a9b4 --- /dev/null +++ b/coderd/agentapi/apps.go @@ -0,0 +1,98 @@ +package agentapi + +import ( + "context" + + "github.com/google/uuid" + "golang.org/x/xerrors" + + "cdr.dev/slog" + agentproto "github.com/coder/coder/v2/agent/proto" + "github.com/coder/coder/v2/coderd/database" +) + +type AppsAPI struct { + AgentFn func(context.Context) (database.WorkspaceAgent, error) + Database database.Store + Log slog.Logger + PublishWorkspaceUpdateFn func(context.Context, *database.WorkspaceAgent) error +} + +func (a *AppsAPI) BatchUpdateAppHealths(ctx context.Context, req *agentproto.BatchUpdateAppHealthRequest) (*agentproto.BatchUpdateAppHealthResponse, error) { + workspaceAgent, err := a.AgentFn(ctx) + if err != nil { + return nil, err + } + + if len(req.Updates) == 0 { + return &agentproto.BatchUpdateAppHealthResponse{}, nil + } + + apps, err := a.Database.GetWorkspaceAppsByAgentID(ctx, workspaceAgent.ID) + if err != nil { + return nil, xerrors.Errorf("get workspace apps by agent ID %q: %w", workspaceAgent.ID, err) + } + + var newApps []database.WorkspaceApp + for _, update := range req.Updates { + updateID, err := uuid.FromBytes(update.Id) + if err != nil { + return nil, xerrors.Errorf("parse workspace app ID %q: %w", update.Id, err) + } + + old := func() *database.WorkspaceApp { + for _, app := range apps { + if app.ID == updateID { + return &app + } + } + + return nil + }() + if old == nil { + return nil, xerrors.Errorf("workspace app ID %q not found", updateID) + } + + if old.HealthcheckUrl == "" { + return nil, xerrors.Errorf("workspace app %q (%q) does not have healthchecks enabled", updateID, old.Slug) + } + + var newHealth database.WorkspaceAppHealth + switch update.Health { + case agentproto.AppHealth_DISABLED: + newHealth = database.WorkspaceAppHealthDisabled + case agentproto.AppHealth_INITIALIZING: + newHealth = database.WorkspaceAppHealthInitializing + case agentproto.AppHealth_HEALTHY: + newHealth = database.WorkspaceAppHealthHealthy + case agentproto.AppHealth_UNHEALTHY: + newHealth = database.WorkspaceAppHealthUnhealthy + default: + return nil, xerrors.Errorf("unknown health status %q for app %q (%q)", update.Health, updateID, old.Slug) + } + + // Don't bother updating if the value hasn't changed. + if old.Health == newHealth { + continue + } + old.Health = newHealth + + newApps = append(newApps, *old) + } + + for _, app := range newApps { + err = a.Database.UpdateWorkspaceAppHealthByID(ctx, database.UpdateWorkspaceAppHealthByIDParams{ + ID: app.ID, + Health: app.Health, + }) + if err != nil { + return nil, xerrors.Errorf("update workspace app health for app %q (%q): %w", err, app.ID, app.Slug) + } + } + + err = a.PublishWorkspaceUpdateFn(ctx, &workspaceAgent) + if err != nil { + return nil, xerrors.Errorf("publish workspace update: %w", err) + } + return &agentproto.BatchUpdateAppHealthResponse{}, nil +} diff --git a/coderd/agentapi/lifecycle.go b/coderd/agentapi/lifecycle.go new file mode 100644 index 0000000000..d909d35eb8 --- /dev/null +++ b/coderd/agentapi/lifecycle.go @@ -0,0 +1,163 @@ +package agentapi + +import ( + "context" + "database/sql" + + "github.com/google/uuid" + "golang.org/x/mod/semver" + "golang.org/x/xerrors" + "google.golang.org/protobuf/types/known/timestamppb" + + "cdr.dev/slog" + agentproto "github.com/coder/coder/v2/agent/proto" + "github.com/coder/coder/v2/coderd/database" + "github.com/coder/coder/v2/coderd/database/dbtime" +) + +type LifecycleAPI struct { + AgentFn func(context.Context) (database.WorkspaceAgent, error) + WorkspaceIDFn func(context.Context, *database.WorkspaceAgent) (uuid.UUID, error) + Database database.Store + Log slog.Logger + PublishWorkspaceUpdateFn func(context.Context, *database.WorkspaceAgent) error +} + +func (a *LifecycleAPI) UpdateLifecycle(ctx context.Context, req *agentproto.UpdateLifecycleRequest) (*agentproto.Lifecycle, error) { + workspaceAgent, err := a.AgentFn(ctx) + if err != nil { + return nil, err + } + workspaceID, err := a.WorkspaceIDFn(ctx, &workspaceAgent) + if err != nil { + return nil, err + } + + logger := a.Log.With( + slog.F("workspace_id", workspaceID), + slog.F("payload", req), + ) + logger.Debug(ctx, "workspace agent state report") + + var lifecycleState database.WorkspaceAgentLifecycleState + switch req.Lifecycle.State { + case agentproto.Lifecycle_CREATED: + lifecycleState = database.WorkspaceAgentLifecycleStateCreated + case agentproto.Lifecycle_STARTING: + lifecycleState = database.WorkspaceAgentLifecycleStateStarting + case agentproto.Lifecycle_START_TIMEOUT: + lifecycleState = database.WorkspaceAgentLifecycleStateStartTimeout + case agentproto.Lifecycle_START_ERROR: + lifecycleState = database.WorkspaceAgentLifecycleStateStartError + case agentproto.Lifecycle_READY: + lifecycleState = database.WorkspaceAgentLifecycleStateReady + case agentproto.Lifecycle_SHUTTING_DOWN: + lifecycleState = database.WorkspaceAgentLifecycleStateShuttingDown + case agentproto.Lifecycle_SHUTDOWN_TIMEOUT: + lifecycleState = database.WorkspaceAgentLifecycleStateShutdownTimeout + case agentproto.Lifecycle_SHUTDOWN_ERROR: + lifecycleState = database.WorkspaceAgentLifecycleStateShutdownError + case agentproto.Lifecycle_OFF: + lifecycleState = database.WorkspaceAgentLifecycleStateOff + default: + return nil, xerrors.Errorf("unknown lifecycle state %q", req.Lifecycle.State) + } + if !lifecycleState.Valid() { + return nil, xerrors.Errorf("unknown lifecycle state %q", req.Lifecycle.State) + } + + changedAt := req.Lifecycle.ChangedAt.AsTime() + if changedAt.IsZero() { + changedAt = dbtime.Now() + req.Lifecycle.ChangedAt = timestamppb.New(changedAt) + } + dbChangedAt := sql.NullTime{Time: changedAt, Valid: true} + + startedAt := workspaceAgent.StartedAt + readyAt := workspaceAgent.ReadyAt + switch lifecycleState { + case database.WorkspaceAgentLifecycleStateStarting: + startedAt = dbChangedAt + readyAt.Valid = false // This agent is re-starting, so it's not ready yet. + case database.WorkspaceAgentLifecycleStateReady, database.WorkspaceAgentLifecycleStateStartError: + readyAt = dbChangedAt + } + + err = a.Database.UpdateWorkspaceAgentLifecycleStateByID(ctx, database.UpdateWorkspaceAgentLifecycleStateByIDParams{ + ID: workspaceAgent.ID, + LifecycleState: lifecycleState, + StartedAt: startedAt, + ReadyAt: readyAt, + }) + if err != nil { + if !xerrors.Is(err, context.Canceled) { + // not an error if we are canceled + logger.Error(ctx, "failed to update lifecycle state", slog.Error(err)) + } + return nil, xerrors.Errorf("update workspace agent lifecycle state: %w", err) + } + + err = a.PublishWorkspaceUpdateFn(ctx, &workspaceAgent) + if err != nil { + return nil, xerrors.Errorf("publish workspace update: %w", err) + } + + return req.Lifecycle, nil +} + +func (a *LifecycleAPI) UpdateStartup(ctx context.Context, req *agentproto.UpdateStartupRequest) (*agentproto.Startup, error) { + workspaceAgent, err := a.AgentFn(ctx) + if err != nil { + return nil, err + } + workspaceID, err := a.WorkspaceIDFn(ctx, &workspaceAgent) + if err != nil { + return nil, err + } + + a.Log.Debug( + ctx, + "post workspace agent version", + slog.F("workspace_id", workspaceID), + slog.F("agent_version", req.Startup.Version), + ) + + if !semver.IsValid(req.Startup.Version) { + return nil, xerrors.Errorf("invalid agent semver version %q", req.Startup.Version) + } + + // Validate subsystems. + dbSubsystems := make([]database.WorkspaceAgentSubsystem, 0, len(req.Startup.Subsystems)) + seenSubsystems := make(map[database.WorkspaceAgentSubsystem]struct{}, len(req.Startup.Subsystems)) + for _, s := range req.Startup.Subsystems { + var dbSubsystem database.WorkspaceAgentSubsystem + switch s { + case agentproto.Startup_ENVBOX: + dbSubsystem = database.WorkspaceAgentSubsystemEnvbox + case agentproto.Startup_ENVBUILDER: + dbSubsystem = database.WorkspaceAgentSubsystemEnvbuilder + case agentproto.Startup_EXECTRACE: + dbSubsystem = database.WorkspaceAgentSubsystemExectrace + default: + return nil, xerrors.Errorf("invalid agent subsystem %q", s) + } + + if _, ok := seenSubsystems[dbSubsystem]; !ok { + seenSubsystems[dbSubsystem] = struct{}{} + dbSubsystems = append(dbSubsystems, dbSubsystem) + } + } + + err = a.Database.UpdateWorkspaceAgentStartupByID(ctx, database.UpdateWorkspaceAgentStartupByIDParams{ + ID: workspaceAgent.ID, + Version: req.Startup.Version, + ExpandedDirectory: req.Startup.ExpandedDirectory, + Subsystems: dbSubsystems, + APIVersion: AgentAPIVersionDRPC, + }) + if err != nil { + return nil, xerrors.Errorf("update workspace agent startup in database: %w", err) + } + + return req.Startup, nil +} diff --git a/coderd/agentapi/logs.go b/coderd/agentapi/logs.go new file mode 100644 index 0000000000..7d34b41e13 --- /dev/null +++ b/coderd/agentapi/logs.go @@ -0,0 +1,139 @@ +package agentapi + +import ( + "context" + + "github.com/google/uuid" + "golang.org/x/xerrors" + + "cdr.dev/slog" + agentproto "github.com/coder/coder/v2/agent/proto" + "github.com/coder/coder/v2/coderd/database" + "github.com/coder/coder/v2/coderd/database/dbtime" + "github.com/coder/coder/v2/codersdk/agentsdk" +) + +type LogsAPI struct { + AgentFn func(context.Context) (database.WorkspaceAgent, error) + Database database.Store + Log slog.Logger + PublishWorkspaceUpdateFn func(context.Context, *database.WorkspaceAgent) error + PublishWorkspaceAgentLogsUpdateFn func(ctx context.Context, workspaceAgentID uuid.UUID, msg agentsdk.LogsNotifyMessage) +} + +func (a *LogsAPI) BatchCreateLogs(ctx context.Context, req *agentproto.BatchCreateLogsRequest) (*agentproto.BatchCreateLogsResponse, error) { + workspaceAgent, err := a.AgentFn(ctx) + if err != nil { + return nil, err + } + + if len(req.Logs) == 0 { + return &agentproto.BatchCreateLogsResponse{}, nil + } + logSourceID, err := uuid.FromBytes(req.LogSourceId) + if err != nil { + return nil, xerrors.Errorf("parse log source ID %q: %w", req.LogSourceId, err) + } + + // This is to support the legacy API where the log source ID was + // not provided in the request body. We default to the external + // log source in this case. + if logSourceID == uuid.Nil { + // Use the external log source + externalSources, err := a.Database.InsertWorkspaceAgentLogSources(ctx, database.InsertWorkspaceAgentLogSourcesParams{ + WorkspaceAgentID: workspaceAgent.ID, + CreatedAt: dbtime.Now(), + ID: []uuid.UUID{agentsdk.ExternalLogSourceID}, + DisplayName: []string{"External"}, + Icon: []string{"/emojis/1f310.png"}, + }) + if database.IsUniqueViolation(err, database.UniqueWorkspaceAgentLogSourcesPkey) { + err = nil + logSourceID = agentsdk.ExternalLogSourceID + } + if err != nil { + return nil, xerrors.Errorf("insert external workspace agent log source: %w", err) + } + if len(externalSources) == 1 { + logSourceID = externalSources[0].ID + } + } + + output := make([]string, 0) + level := make([]database.LogLevel, 0) + outputLength := 0 + for _, logEntry := range req.Logs { + output = append(output, logEntry.Output) + outputLength += len(logEntry.Output) + + var dbLevel database.LogLevel + switch logEntry.Level { + case agentproto.Log_TRACE: + dbLevel = database.LogLevelTrace + case agentproto.Log_DEBUG: + dbLevel = database.LogLevelDebug + case agentproto.Log_INFO: + dbLevel = database.LogLevelInfo + case agentproto.Log_WARN: + dbLevel = database.LogLevelWarn + case agentproto.Log_ERROR: + dbLevel = database.LogLevelError + default: + // Default to "info" to support older clients that didn't have the + // level field. + dbLevel = database.LogLevelInfo + } + level = append(level, dbLevel) + } + + logs, err := a.Database.InsertWorkspaceAgentLogs(ctx, database.InsertWorkspaceAgentLogsParams{ + AgentID: workspaceAgent.ID, + CreatedAt: dbtime.Now(), + Output: output, + Level: level, + LogSourceID: logSourceID, + OutputLength: int32(outputLength), + }) + if err != nil { + if !database.IsWorkspaceAgentLogsLimitError(err) { + return nil, xerrors.Errorf("insert workspace agent logs: %w", err) + } + if workspaceAgent.LogsOverflowed { + return nil, xerrors.New("workspace agent logs overflowed") + } + err := a.Database.UpdateWorkspaceAgentLogOverflowByID(ctx, database.UpdateWorkspaceAgentLogOverflowByIDParams{ + ID: workspaceAgent.ID, + LogsOverflowed: true, + }) + if err != nil { + // We don't want to return here, because the agent will retry on + // failure and this isn't a huge deal. The overflow state is just a + // hint to the user that the logs are incomplete. + a.Log.Warn(ctx, "failed to update workspace agent log overflow", slog.Error(err)) + } + + err = a.PublishWorkspaceUpdateFn(ctx, &workspaceAgent) + if err != nil { + return nil, xerrors.Errorf("publish workspace update: %w", err) + } + return nil, xerrors.New("workspace agent log limit exceeded") + } + + // Publish by the lowest log ID inserted so the log stream will fetch + // everything from that point. + lowestLogID := logs[0].ID + a.PublishWorkspaceAgentLogsUpdateFn(ctx, workspaceAgent.ID, agentsdk.LogsNotifyMessage{ + CreatedAfter: lowestLogID - 1, + }) + + if workspaceAgent.LogsLength == 0 { + // If these are the first logs being appended, we publish a UI update + // to notify the UI that logs are now available. + err = a.PublishWorkspaceUpdateFn(ctx, &workspaceAgent) + if err != nil { + return nil, xerrors.Errorf("publish workspace update: %w", err) + } + } + + return &agentproto.BatchCreateLogsResponse{}, nil +} diff --git a/coderd/agentapi/manifest.go b/coderd/agentapi/manifest.go new file mode 100644 index 0000000000..a25d1df26e --- /dev/null +++ b/coderd/agentapi/manifest.go @@ -0,0 +1,153 @@ +package agentapi + +import ( + "context" + "database/sql" + "fmt" + "net/url" + "strings" + "sync/atomic" + "time" + + "github.com/google/uuid" + "golang.org/x/sync/errgroup" + "golang.org/x/xerrors" + "tailscale.com/tailcfg" + + agentproto "github.com/coder/coder/v2/agent/proto" + "github.com/coder/coder/v2/coderd/database" + "github.com/coder/coder/v2/coderd/database/db2sdk" + "github.com/coder/coder/v2/coderd/database/dbauthz" + "github.com/coder/coder/v2/coderd/externalauth" + "github.com/coder/coder/v2/coderd/httpapi" + "github.com/coder/coder/v2/codersdk" + "github.com/coder/coder/v2/tailnet" +) + +type ManifestAPI struct { + AccessURL *url.URL + AppHostname string + AgentInactiveDisconnectTimeout time.Duration + AgentFallbackTroubleshootingURL string + ExternalAuthConfigs []*externalauth.Config + DisableDirectConnections bool + DerpForceWebSockets bool + + AgentFn func(context.Context) (database.WorkspaceAgent, error) + Database database.Store + DerpMapFn func() *tailcfg.DERPMap + TailnetCoordinator *atomic.Pointer[tailnet.Coordinator] +} + +func (a *ManifestAPI) GetManifest(ctx context.Context, _ *agentproto.GetManifestRequest) (*agentproto.Manifest, error) { + workspaceAgent, err := a.AgentFn(ctx) + if err != nil { + return nil, err + } + + apiAgent, err := db2sdk.WorkspaceAgent( + a.DerpMapFn(), *a.TailnetCoordinator.Load(), workspaceAgent, nil, nil, nil, a.AgentInactiveDisconnectTimeout, + a.AgentFallbackTroubleshootingURL, + ) + if err != nil { + return nil, xerrors.Errorf("converting workspace agent: %w", err) + } + + var ( + dbApps []database.WorkspaceApp + scripts []database.WorkspaceAgentScript + metadata []database.WorkspaceAgentMetadatum + resource database.WorkspaceResource + build database.WorkspaceBuild + workspace database.Workspace + owner database.User + ) + + var eg errgroup.Group + eg.Go(func() (err error) { + dbApps, err = a.Database.GetWorkspaceAppsByAgentID(ctx, workspaceAgent.ID) + if err != nil && !xerrors.Is(err, sql.ErrNoRows) { + return err + } + return nil + }) + eg.Go(func() (err error) { + // nolint:gocritic // This is necessary to fetch agent scripts! + scripts, err = a.Database.GetWorkspaceAgentScriptsByAgentIDs(dbauthz.AsSystemRestricted(ctx), []uuid.UUID{workspaceAgent.ID}) + return err + }) + eg.Go(func() (err error) { + metadata, err = a.Database.GetWorkspaceAgentMetadata(ctx, database.GetWorkspaceAgentMetadataParams{ + WorkspaceAgentID: workspaceAgent.ID, + Keys: nil, + }) + return err + }) + eg.Go(func() (err error) { + resource, err = a.Database.GetWorkspaceResourceByID(ctx, workspaceAgent.ResourceID) + if err != nil { + return xerrors.Errorf("getting resource by id: %w", err) + } + build, err = a.Database.GetWorkspaceBuildByJobID(ctx, resource.JobID) + if err != nil { + return xerrors.Errorf("getting workspace build by job id: %w", err) + } + workspace, err = a.Database.GetWorkspaceByID(ctx, build.WorkspaceID) + if err != nil { + return xerrors.Errorf("getting workspace by id: %w", err) + } + owner, err = a.Database.GetUserByID(ctx, workspace.OwnerID) + if err != nil { + return xerrors.Errorf("getting workspace owner by id: %w", err) + } + return err + }) + err = eg.Wait() + if err != nil { + return nil, xerrors.Errorf("fetching workspace agent data: %w", err) + } + + appHost := httpapi.ApplicationURL{ + AppSlugOrPort: "{{port}}", + AgentName: workspaceAgent.Name, + WorkspaceName: workspace.Name, + Username: owner.Username, + } + vscodeProxyURI := a.AccessURL.Scheme + "://" + strings.ReplaceAll(a.AppHostname, "*", appHost.String()) + if a.AppHostname == "" { + vscodeProxyURI += a.AccessURL.Hostname() + } + if a.AccessURL.Port() != "" { + vscodeProxyURI += fmt.Sprintf(":%s", a.AccessURL.Port()) + } + + var gitAuthConfigs uint32 + for _, cfg := range a.ExternalAuthConfigs { + if codersdk.EnhancedExternalAuthProvider(cfg.Type).Git() { + gitAuthConfigs++ + } + } + + apps, err := agentproto.DBAppsToProto(dbApps, workspaceAgent, owner.Username, workspace) + if err != nil { + return nil, xerrors.Errorf("converting workspace apps: %w", err) + } + + return &agentproto.Manifest{ + AgentId: workspaceAgent.ID[:], + OwnerUsername: owner.Username, + WorkspaceId: workspace.ID[:], + GitAuthConfigs: gitAuthConfigs, + EnvironmentVariables: apiAgent.EnvironmentVariables, + Directory: apiAgent.Directory, + VsCodePortProxyUri: vscodeProxyURI, + MotdPath: workspaceAgent.MOTDFile, + DisableDirectConnections: a.DisableDirectConnections, + DerpForceWebsockets: a.DerpForceWebSockets, + + DerpMap: tailnet.DERPMapToProto(a.DerpMapFn()), + Scripts: agentproto.DBAgentScriptsToProto(scripts), + Apps: apps, + Metadata: agentproto.DBAgentMetadataToProtoDescription(metadata), + }, nil +} diff --git a/coderd/agentapi/metadata.go b/coderd/agentapi/metadata.go new file mode 100644 index 0000000000..a3bf24b203 --- /dev/null +++ b/coderd/agentapi/metadata.go @@ -0,0 +1,115 @@ +package agentapi + +import ( + "context" + "encoding/json" + "fmt" + "time" + + "github.com/google/uuid" + "golang.org/x/xerrors" + + "cdr.dev/slog" + agentproto "github.com/coder/coder/v2/agent/proto" + "github.com/coder/coder/v2/coderd/database" + "github.com/coder/coder/v2/coderd/database/pubsub" +) + +type MetadataAPI struct { + AgentFn func(context.Context) (database.WorkspaceAgent, error) + Database database.Store + Pubsub pubsub.Pubsub + Log slog.Logger +} + +func (a *MetadataAPI) BatchUpdateMetadata(ctx context.Context, req *agentproto.BatchUpdateMetadataRequest) (*agentproto.BatchUpdateMetadataResponse, error) { + const ( + // maxValueLen is set to 2048 to stay under the 8000 byte Postgres + // NOTIFY limit. Since both value and error can be set, the real payload + // limit is 2 * 2048 * 4/3 = 5461 bytes + a few + // hundred bytes for JSON syntax, key names, and metadata. + maxValueLen = 2048 + maxErrorLen = maxValueLen + ) + + workspaceAgent, err := a.AgentFn(ctx) + if err != nil { + return nil, err + } + + collectedAt := time.Now() + dbUpdate := database.UpdateWorkspaceAgentMetadataParams{ + WorkspaceAgentID: workspaceAgent.ID, + Key: make([]string, 0, len(req.Metadata)), + Value: make([]string, 0, len(req.Metadata)), + Error: make([]string, 0, len(req.Metadata)), + CollectedAt: make([]time.Time, 0, len(req.Metadata)), + } + + for _, md := range req.Metadata { + metadataError := md.Result.Error + + // We overwrite the error if the provided payload is too long. + if len(md.Result.Value) > maxValueLen { + metadataError = fmt.Sprintf("value of %d bytes exceeded %d bytes", len(md.Result.Value), maxValueLen) + md.Result.Value = md.Result.Value[:maxValueLen] + } + + if len(md.Result.Error) > maxErrorLen { + metadataError = fmt.Sprintf("error of %d bytes exceeded %d bytes", len(md.Result.Error), maxErrorLen) + md.Result.Error = "" + } + + // We don't want a misconfigured agent to fill the database. + dbUpdate.Key = append(dbUpdate.Key, md.Key) + dbUpdate.Value = append(dbUpdate.Value, md.Result.Value) + dbUpdate.Error = append(dbUpdate.Error, metadataError) + // We ignore the CollectedAt from the agent to avoid bugs caused by + // clock skew. + dbUpdate.CollectedAt = append(dbUpdate.CollectedAt, collectedAt) + + a.Log.Debug( + ctx, "accepted metadata report", + slog.F("collected_at", collectedAt), + slog.F("original_collected_at", collectedAt), + slog.F("key", md.Key), + slog.F("value", ellipse(md.Result.Value, 16)), + ) + } + + payload, err := json.Marshal(WorkspaceAgentMetadataChannelPayload{ + CollectedAt: collectedAt, + Keys: dbUpdate.Key, + }) + if err != nil { + return nil, xerrors.Errorf("marshal workspace agent metadata channel payload: %w", err) + } + + err = a.Database.UpdateWorkspaceAgentMetadata(ctx, dbUpdate) + if err != nil { + return nil, xerrors.Errorf("update workspace agent metadata in database: %w", err) + } + + err = a.Pubsub.Publish(WatchWorkspaceAgentMetadataChannel(workspaceAgent.ID), payload) + if err != nil { + return nil, xerrors.Errorf("publish workspace agent metadata: %w", err) + } + + return &agentproto.BatchUpdateMetadataResponse{}, nil +} + +func ellipse(v string, n int) string { + if len(v) > n { + return v[:n] + "..." + } + return v +} + +type WorkspaceAgentMetadataChannelPayload struct { + CollectedAt time.Time `json:"collected_at"` + Keys []string `json:"keys"` +} + +func WatchWorkspaceAgentMetadataChannel(id uuid.UUID) string { + return "workspace_agent_metadata:" + id.String() +} diff --git a/coderd/agentapi/servicebanner.go b/coderd/agentapi/servicebanner.go new file mode 100644 index 0000000000..b123e78fc1 --- /dev/null +++ b/coderd/agentapi/servicebanner.go @@ -0,0 +1,38 @@ +package agentapi + +import ( + "context" + "database/sql" + "encoding/json" + + "golang.org/x/xerrors" + + agentproto "github.com/coder/coder/v2/agent/proto" + "github.com/coder/coder/v2/coderd/database" + "github.com/coder/coder/v2/codersdk" +) + +type ServiceBannerAPI struct { + Database database.Store +} + +func (a *ServiceBannerAPI) GetServiceBanner(ctx context.Context, _ *agentproto.GetServiceBannerRequest) (*agentproto.ServiceBanner, error) { + serviceBannerJSON, err := a.Database.GetServiceBanner(ctx) + if err != nil && !xerrors.Is(err, sql.ErrNoRows) { + return nil, xerrors.Errorf("get service banner: %w", err) + } + + var cfg codersdk.ServiceBannerConfig + if serviceBannerJSON != "" { + err = json.Unmarshal([]byte(serviceBannerJSON), &cfg) + if err != nil { + return nil, xerrors.Errorf("unmarshal json: %w, raw: %s", err, serviceBannerJSON) + } + } + + return &agentproto.ServiceBanner{ + Enabled: cfg.Enabled, + Message: cfg.Message, + BackgroundColor: cfg.BackgroundColor, + }, nil +} diff --git a/coderd/agentapi/stats.go b/coderd/agentapi/stats.go new file mode 100644 index 0000000000..05c1b744f2 --- /dev/null +++ b/coderd/agentapi/stats.go @@ -0,0 +1,121 @@ +package agentapi + +import ( + "context" + "sync/atomic" + "time" + + "golang.org/x/sync/errgroup" + "golang.org/x/xerrors" + "google.golang.org/protobuf/types/known/durationpb" + + "cdr.dev/slog" + agentproto "github.com/coder/coder/v2/agent/proto" + "github.com/coder/coder/v2/coderd/autobuild" + "github.com/coder/coder/v2/coderd/batchstats" + "github.com/coder/coder/v2/coderd/database" + "github.com/coder/coder/v2/coderd/database/dbtime" + "github.com/coder/coder/v2/coderd/prometheusmetrics" + "github.com/coder/coder/v2/coderd/schedule" +) + +type StatsAPI struct { + AgentFn func(context.Context) (database.WorkspaceAgent, error) + Database database.Store + Log slog.Logger + StatsBatcher *batchstats.Batcher + TemplateScheduleStore *atomic.Pointer[schedule.TemplateScheduleStore] + AgentStatsRefreshInterval time.Duration + UpdateAgentMetricsFn func(ctx context.Context, labels prometheusmetrics.AgentMetricLabels, metrics []*agentproto.Stats_Metric) +} + +func (a *StatsAPI) UpdateStats(ctx context.Context, req *agentproto.UpdateStatsRequest) (*agentproto.UpdateStatsResponse, error) { + workspaceAgent, err := a.AgentFn(ctx) + if err != nil { + return nil, err + } + row, err := a.Database.GetWorkspaceByAgentID(ctx, workspaceAgent.ID) + if err != nil { + return nil, xerrors.Errorf("get workspace by agent ID %q: %w", workspaceAgent.ID, err) + } + workspace := row.Workspace + + res := &agentproto.UpdateStatsResponse{ + ReportInterval: durationpb.New(a.AgentStatsRefreshInterval), + } + + // An empty stat means it's just looking for the report interval. + if len(req.Stats.ConnectionsByProto) == 0 { + return res, nil + } + + a.Log.Debug(ctx, "read stats report", + slog.F("interval", a.AgentStatsRefreshInterval), + slog.F("workspace_id", workspace.ID), + slog.F("payload", req), + ) + + if req.Stats.ConnectionCount > 0 { + var nextAutostart time.Time + if workspace.AutostartSchedule.String != "" { + templateSchedule, err := (*(a.TemplateScheduleStore.Load())).Get(ctx, a.Database, workspace.TemplateID) + // If the template schedule fails to load, just default to bumping without the next trasition and log it. + if err != nil { + a.Log.Warn(ctx, "failed to load template schedule bumping activity, defaulting to bumping by 60min", + slog.F("workspace_id", workspace.ID), + slog.F("template_id", workspace.TemplateID), + slog.Error(err), + ) + } else { + next, allowed := autobuild.NextAutostartSchedule(time.Now(), workspace.AutostartSchedule.String, templateSchedule) + if allowed { + nextAutostart = next + } + } + } + ActivityBumpWorkspace(ctx, a.Log.Named("activity_bump"), a.Database, workspace.ID, nextAutostart) + } + + now := dbtime.Now() + + var errGroup errgroup.Group + errGroup.Go(func() error { + if err := a.StatsBatcher.Add(time.Now(), workspaceAgent.ID, workspace.TemplateID, workspace.OwnerID, workspace.ID, req.Stats); err != nil { + a.Log.Error(ctx, "failed to add stats to batcher", slog.Error(err)) + return xerrors.Errorf("can't insert workspace agent stat: %w", err) + } + return nil + }) + errGroup.Go(func() error { + err := a.Database.UpdateWorkspaceLastUsedAt(ctx, database.UpdateWorkspaceLastUsedAtParams{ + ID: workspace.ID, + LastUsedAt: now, + }) + if err != nil { + return xerrors.Errorf("can't update workspace LastUsedAt: %w", err) + } + return nil + }) + if a.UpdateAgentMetricsFn != nil { + errGroup.Go(func() error { + user, err := a.Database.GetUserByID(ctx, workspace.OwnerID) + if err != nil { + return xerrors.Errorf("can't get user: %w", err) + } + + a.UpdateAgentMetricsFn(ctx, prometheusmetrics.AgentMetricLabels{ + Username: user.Username, + WorkspaceName: workspace.Name, + AgentName: workspaceAgent.Name, + TemplateName: row.TemplateName, + }, req.Stats.Metrics) + return nil + }) + } + err = errGroup.Wait() + if err != nil { + return nil, xerrors.Errorf("update stats in database: %w", err) + } + + return res, nil +} diff --git a/coderd/agentapi/tailnet.go b/coderd/agentapi/tailnet.go new file mode 100644 index 0000000000..d803fc4bd8 --- /dev/null +++ b/coderd/agentapi/tailnet.go @@ -0,0 +1,53 @@ +package agentapi + +import ( + "context" + "time" + + "golang.org/x/xerrors" + "tailscale.com/tailcfg" + + agentproto "github.com/coder/coder/v2/agent/proto" + "github.com/coder/coder/v2/tailnet" + tailnetproto "github.com/coder/coder/v2/tailnet/proto" +) + +type TailnetAPI struct { + Ctx context.Context + DerpMapFn func() *tailcfg.DERPMap + DerpMapUpdateFrequency time.Duration +} + +func (a *TailnetAPI) StreamDERPMaps(_ *tailnetproto.StreamDERPMapsRequest, stream agentproto.DRPCAgent_StreamDERPMapsStream) error { + defer stream.Close() + + ticker := time.NewTicker(a.DerpMapUpdateFrequency) + defer ticker.Stop() + + var lastDERPMap *tailcfg.DERPMap + for { + derpMap := a.DerpMapFn() + if lastDERPMap == nil || !tailnet.CompareDERPMaps(lastDERPMap, derpMap) { + protoDERPMap := tailnet.DERPMapToProto(derpMap) + err := stream.Send(protoDERPMap) + if err != nil { + return xerrors.Errorf("send derp map: %w", err) + } + lastDERPMap = derpMap + } + + ticker.Reset(a.DerpMapUpdateFrequency) + select { + case <-stream.Context().Done(): + return nil + case <-a.Ctx.Done(): + return nil + case <-ticker.C: + } + } +} + +func (*TailnetAPI) CoordinateTailnet(_ agentproto.DRPCAgent_CoordinateTailnetStream) error { + // TODO: implement this + return xerrors.New("CoordinateTailnet is unimplemented") +} diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index 0bf86fc999..091ac4927d 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -5269,6 +5269,28 @@ const docTemplate = `{ } } }, + "/workspaceagents/me/rpc": { + "get": { + "security": [ + { + "CoderSessionToken": [] + } + ], + "tags": [ + "Agents" + ], + "summary": "Workspace agent RPC API", + "operationId": "workspace-agent-rpc-api", + "responses": { + "101": { + "description": "Switching Protocols" + } + }, + "x-apidocgen": { + "skip": true + } + } + }, "/workspaceagents/me/startup": { "post": { "security": [ diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index 2187fd7a05..fd0703d4e7 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -4633,6 +4633,26 @@ } } }, + "/workspaceagents/me/rpc": { + "get": { + "security": [ + { + "CoderSessionToken": [] + } + ], + "tags": ["Agents"], + "summary": "Workspace agent RPC API", + "operationId": "workspace-agent-rpc-api", + "responses": { + "101": { + "description": "Switching Protocols" + } + }, + "x-apidocgen": { + "skip": true + } + } + }, "/workspaceagents/me/startup": { "post": { "security": [ diff --git a/coderd/batchstats/batcher.go b/coderd/batchstats/batcher.go index cc234c693e..34b76d239e 100644 --- a/coderd/batchstats/batcher.go +++ b/coderd/batchstats/batcher.go @@ -13,10 +13,10 @@ import ( "cdr.dev/slog" "cdr.dev/slog/sloggers/sloghuman" + agentproto "github.com/coder/coder/v2/agent/proto" "github.com/coder/coder/v2/coderd/database" "github.com/coder/coder/v2/coderd/database/dbauthz" "github.com/coder/coder/v2/coderd/database/dbtime" - "github.com/coder/coder/v2/codersdk/agentsdk" ) const ( @@ -133,7 +133,7 @@ func (b *Batcher) Add( templateID uuid.UUID, userID uuid.UUID, workspaceID uuid.UUID, - st agentsdk.Stats, + st *agentproto.Stats, ) error { b.mu.Lock() defer b.mu.Unlock() @@ -156,11 +156,11 @@ func (b *Batcher) Add( b.buf.RxBytes = append(b.buf.RxBytes, st.RxBytes) b.buf.TxPackets = append(b.buf.TxPackets, st.TxPackets) b.buf.TxBytes = append(b.buf.TxBytes, st.TxBytes) - b.buf.SessionCountVSCode = append(b.buf.SessionCountVSCode, st.SessionCountVSCode) - b.buf.SessionCountJetBrains = append(b.buf.SessionCountJetBrains, st.SessionCountJetBrains) - b.buf.SessionCountReconnectingPTY = append(b.buf.SessionCountReconnectingPTY, st.SessionCountReconnectingPTY) - b.buf.SessionCountSSH = append(b.buf.SessionCountSSH, st.SessionCountSSH) - b.buf.ConnectionMedianLatencyMS = append(b.buf.ConnectionMedianLatencyMS, st.ConnectionMedianLatencyMS) + b.buf.SessionCountVSCode = append(b.buf.SessionCountVSCode, st.SessionCountVscode) + b.buf.SessionCountJetBrains = append(b.buf.SessionCountJetBrains, st.SessionCountJetbrains) + b.buf.SessionCountReconnectingPTY = append(b.buf.SessionCountReconnectingPTY, st.SessionCountReconnectingPty) + b.buf.SessionCountSSH = append(b.buf.SessionCountSSH, st.SessionCountSsh) + b.buf.ConnectionMedianLatencyMS = append(b.buf.ConnectionMedianLatencyMS, st.ConnectionMedianLatencyMs) // If the buffer is over 80% full, signal the flusher to flush immediately. // We want to trigger flushes early to reduce the likelihood of diff --git a/coderd/batchstats/batcher_internal_test.go b/coderd/batchstats/batcher_internal_test.go index f9bc9e1372..8954fa5455 100644 --- a/coderd/batchstats/batcher_internal_test.go +++ b/coderd/batchstats/batcher_internal_test.go @@ -10,13 +10,13 @@ import ( "cdr.dev/slog" "cdr.dev/slog/sloggers/slogtest" + agentproto "github.com/coder/coder/v2/agent/proto" "github.com/coder/coder/v2/coderd/database" "github.com/coder/coder/v2/coderd/database/dbgen" "github.com/coder/coder/v2/coderd/database/dbtestutil" "github.com/coder/coder/v2/coderd/database/dbtime" "github.com/coder/coder/v2/coderd/database/pubsub" "github.com/coder/coder/v2/coderd/rbac" - "github.com/coder/coder/v2/codersdk/agentsdk" "github.com/coder/coder/v2/cryptorand" ) @@ -63,7 +63,7 @@ func TestBatchStats(t *testing.T) { // Given: a single data point is added for workspace t2 := t1.Add(time.Second) t.Logf("inserting 1 stat") - require.NoError(t, b.Add(t2.Add(time.Millisecond), deps1.Agent.ID, deps1.User.ID, deps1.Template.ID, deps1.Workspace.ID, randAgentSDKStats(t))) + require.NoError(t, b.Add(t2.Add(time.Millisecond), deps1.Agent.ID, deps1.User.ID, deps1.Template.ID, deps1.Workspace.ID, randStats(t))) // When: it becomes time to report stats // Signal a tick and wait for a flush to complete. @@ -87,9 +87,9 @@ func TestBatchStats(t *testing.T) { t.Logf("inserting %d stats", defaultBufferSize) for i := 0; i < defaultBufferSize; i++ { if i%2 == 0 { - require.NoError(t, b.Add(t3.Add(time.Millisecond), deps1.Agent.ID, deps1.User.ID, deps1.Template.ID, deps1.Workspace.ID, randAgentSDKStats(t))) + require.NoError(t, b.Add(t3.Add(time.Millisecond), deps1.Agent.ID, deps1.User.ID, deps1.Template.ID, deps1.Workspace.ID, randStats(t))) } else { - require.NoError(t, b.Add(t3.Add(time.Millisecond), deps2.Agent.ID, deps2.User.ID, deps2.Template.ID, deps2.Workspace.ID, randAgentSDKStats(t))) + require.NoError(t, b.Add(t3.Add(time.Millisecond), deps2.Agent.ID, deps2.User.ID, deps2.Template.ID, deps2.Workspace.ID, randStats(t))) } } }() @@ -129,10 +129,10 @@ func TestBatchStats(t *testing.T) { require.Equal(t, defaultBufferSize, cap(b.buf.ID), "buffer grew beyond expected capacity") } -// randAgentSDKStats returns a random agentsdk.Stats -func randAgentSDKStats(t *testing.T, opts ...func(*agentsdk.Stats)) agentsdk.Stats { +// randStats returns a random agentproto.Stats +func randStats(t *testing.T, opts ...func(*agentproto.Stats)) *agentproto.Stats { t.Helper() - s := agentsdk.Stats{ + s := &agentproto.Stats{ ConnectionsByProto: map[string]int64{ "ssh": mustRandInt64n(t, 9) + 1, "vscode": mustRandInt64n(t, 9) + 1, @@ -140,19 +140,19 @@ func randAgentSDKStats(t *testing.T, opts ...func(*agentsdk.Stats)) agentsdk.Sta "reconnecting_pty": mustRandInt64n(t, 9) + 1, }, ConnectionCount: mustRandInt64n(t, 99) + 1, - ConnectionMedianLatencyMS: float64(mustRandInt64n(t, 99) + 1), + ConnectionMedianLatencyMs: float64(mustRandInt64n(t, 99) + 1), RxPackets: mustRandInt64n(t, 99) + 1, RxBytes: mustRandInt64n(t, 99) + 1, TxPackets: mustRandInt64n(t, 99) + 1, TxBytes: mustRandInt64n(t, 99) + 1, - SessionCountVSCode: mustRandInt64n(t, 9) + 1, - SessionCountJetBrains: mustRandInt64n(t, 9) + 1, - SessionCountReconnectingPTY: mustRandInt64n(t, 9) + 1, - SessionCountSSH: mustRandInt64n(t, 9) + 1, - Metrics: []agentsdk.AgentMetric{}, + SessionCountVscode: mustRandInt64n(t, 9) + 1, + SessionCountJetbrains: mustRandInt64n(t, 9) + 1, + SessionCountReconnectingPty: mustRandInt64n(t, 9) + 1, + SessionCountSsh: mustRandInt64n(t, 9) + 1, + Metrics: []*agentproto.Stats_Metric{}, } for _, opt := range opts { - opt(&s) + opt(s) } return s } diff --git a/coderd/coderd.go b/coderd/coderd.go index 0e7421aa2d..67d1593b4f 100644 --- a/coderd/coderd.go +++ b/coderd/coderd.go @@ -16,6 +16,10 @@ import ( "sync/atomic" "time" + "github.com/coder/coder/v2/coderd/prometheusmetrics" + + agentproto "github.com/coder/coder/v2/agent/proto" + "github.com/andybalholm/brotli" "github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5/middleware" @@ -34,24 +38,22 @@ import ( "tailscale.com/types/key" "tailscale.com/util/singleflight" - // Used for swagger docs. - _ "github.com/coder/coder/v2/coderd/apidoc" - "github.com/coder/coder/v2/coderd/externalauth" - "github.com/coder/coder/v2/coderd/healthcheck/derphealth" - "github.com/coder/coder/v2/coderd/prometheusmetrics" - "cdr.dev/slog" - "github.com/coder/coder/v2/buildinfo" "github.com/coder/coder/v2/cli/clibase" + + // Used for swagger docs. + _ "github.com/coder/coder/v2/coderd/apidoc" "github.com/coder/coder/v2/coderd/audit" "github.com/coder/coder/v2/coderd/awsidentity" "github.com/coder/coder/v2/coderd/batchstats" "github.com/coder/coder/v2/coderd/database" "github.com/coder/coder/v2/coderd/database/dbauthz" "github.com/coder/coder/v2/coderd/database/pubsub" + "github.com/coder/coder/v2/coderd/externalauth" "github.com/coder/coder/v2/coderd/gitsshkey" "github.com/coder/coder/v2/coderd/healthcheck" + "github.com/coder/coder/v2/coderd/healthcheck/derphealth" "github.com/coder/coder/v2/coderd/httpapi" "github.com/coder/coder/v2/coderd/httpmw" "github.com/coder/coder/v2/coderd/metricscache" @@ -65,7 +67,6 @@ import ( "github.com/coder/coder/v2/coderd/workspaceapps" "github.com/coder/coder/v2/coderd/wsconncache" "github.com/coder/coder/v2/codersdk" - "github.com/coder/coder/v2/codersdk/agentsdk" "github.com/coder/coder/v2/codersdk/drpc" "github.com/coder/coder/v2/provisionerd/proto" "github.com/coder/coder/v2/provisionersdk" @@ -171,7 +172,7 @@ type Options struct { HTTPClient *http.Client - UpdateAgentMetrics func(ctx context.Context, labels prometheusmetrics.AgentMetricLabels, metrics []agentsdk.AgentMetric) + UpdateAgentMetrics func(ctx context.Context, labels prometheusmetrics.AgentMetricLabels, metrics []*agentproto.Stats_Metric) StatsBatcher *batchstats.Batcher WorkspaceAppsStatsCollectorOptions workspaceapps.StatsCollectorOptions @@ -388,6 +389,7 @@ func New(options *Options) *API { ), metricsCache: metricsCache, Auditor: atomic.Pointer[audit.Auditor]{}, + TailnetCoordinator: atomic.Pointer[tailnet.Coordinator]{}, TemplateScheduleStore: options.TemplateScheduleStore, UserQuietHoursScheduleStore: options.UserQuietHoursScheduleStore, AccessControlStore: options.AccessControlStore, @@ -866,6 +868,7 @@ func New(options *Options) *API { DB: options.Database, Optional: false, })) + r.Get("/rpc", api.workspaceAgentRPC) r.Get("/manifest", api.workspaceAgentManifest) // This route is deprecated and will be removed in a future release. // New agents will use /me/manifest instead. diff --git a/coderd/database/db2sdk/db2sdk.go b/coderd/database/db2sdk/db2sdk.go index 324ba781ab..ccf67ea98d 100644 --- a/coderd/database/db2sdk/db2sdk.go +++ b/coderd/database/db2sdk/db2sdk.go @@ -3,17 +3,23 @@ package db2sdk import ( "encoding/json" + "fmt" + "strconv" "strings" + "time" "github.com/google/uuid" "golang.org/x/exp/slices" "golang.org/x/xerrors" + "tailscale.com/tailcfg" "github.com/coder/coder/v2/coderd/database" + "github.com/coder/coder/v2/coderd/httpapi" "github.com/coder/coder/v2/coderd/parameter" "github.com/coder/coder/v2/coderd/rbac" "github.com/coder/coder/v2/codersdk" "github.com/coder/coder/v2/provisionersdk/proto" + "github.com/coder/coder/v2/tailnet" ) type ExternalAuthMeta struct { @@ -218,3 +224,178 @@ func templateVersionParameterOptions(rawOptions json.RawMessage) ([]codersdk.Tem } return options, nil } + +func convertDisplayApps(apps []database.DisplayApp) []codersdk.DisplayApp { + dapps := make([]codersdk.DisplayApp, 0, len(apps)) + for _, app := range apps { + switch codersdk.DisplayApp(app) { + case codersdk.DisplayAppVSCodeDesktop, codersdk.DisplayAppVSCodeInsiders, codersdk.DisplayAppPortForward, codersdk.DisplayAppWebTerminal, codersdk.DisplayAppSSH: + dapps = append(dapps, codersdk.DisplayApp(app)) + } + } + + return dapps +} + +func WorkspaceAgent(derpMap *tailcfg.DERPMap, coordinator tailnet.Coordinator, + dbAgent database.WorkspaceAgent, apps []codersdk.WorkspaceApp, scripts []codersdk.WorkspaceAgentScript, logSources []codersdk.WorkspaceAgentLogSource, + agentInactiveDisconnectTimeout time.Duration, agentFallbackTroubleshootingURL string, +) (codersdk.WorkspaceAgent, error) { + var envs map[string]string + if dbAgent.EnvironmentVariables.Valid { + err := json.Unmarshal(dbAgent.EnvironmentVariables.RawMessage, &envs) + if err != nil { + return codersdk.WorkspaceAgent{}, xerrors.Errorf("unmarshal env vars: %w", err) + } + } + troubleshootingURL := agentFallbackTroubleshootingURL + if dbAgent.TroubleshootingURL != "" { + troubleshootingURL = dbAgent.TroubleshootingURL + } + subsystems := make([]codersdk.AgentSubsystem, len(dbAgent.Subsystems)) + for i, subsystem := range dbAgent.Subsystems { + subsystems[i] = codersdk.AgentSubsystem(subsystem) + } + + legacyStartupScriptBehavior := codersdk.WorkspaceAgentStartupScriptBehaviorNonBlocking + for _, script := range scripts { + if !script.RunOnStart { + continue + } + if !script.StartBlocksLogin { + continue + } + legacyStartupScriptBehavior = codersdk.WorkspaceAgentStartupScriptBehaviorBlocking + } + + workspaceAgent := codersdk.WorkspaceAgent{ + ID: dbAgent.ID, + CreatedAt: dbAgent.CreatedAt, + UpdatedAt: dbAgent.UpdatedAt, + ResourceID: dbAgent.ResourceID, + InstanceID: dbAgent.AuthInstanceID.String, + Name: dbAgent.Name, + Architecture: dbAgent.Architecture, + OperatingSystem: dbAgent.OperatingSystem, + Scripts: scripts, + StartupScriptBehavior: legacyStartupScriptBehavior, + LogsLength: dbAgent.LogsLength, + LogsOverflowed: dbAgent.LogsOverflowed, + LogSources: logSources, + Version: dbAgent.Version, + APIVersion: dbAgent.APIVersion, + EnvironmentVariables: envs, + Directory: dbAgent.Directory, + ExpandedDirectory: dbAgent.ExpandedDirectory, + Apps: apps, + ConnectionTimeoutSeconds: dbAgent.ConnectionTimeoutSeconds, + TroubleshootingURL: troubleshootingURL, + LifecycleState: codersdk.WorkspaceAgentLifecycle(dbAgent.LifecycleState), + Subsystems: subsystems, + DisplayApps: convertDisplayApps(dbAgent.DisplayApps), + } + node := coordinator.Node(dbAgent.ID) + if node != nil { + workspaceAgent.DERPLatency = map[string]codersdk.DERPRegion{} + for rawRegion, latency := range node.DERPLatency { + regionParts := strings.SplitN(rawRegion, "-", 2) + regionID, err := strconv.Atoi(regionParts[0]) + if err != nil { + return codersdk.WorkspaceAgent{}, xerrors.Errorf("convert derp region id %q: %w", rawRegion, err) + } + region, found := derpMap.Regions[regionID] + if !found { + // It's possible that a workspace agent is using an old DERPMap + // and reports regions that do not exist. If that's the case, + // report the region as unknown! + region = &tailcfg.DERPRegion{ + RegionID: regionID, + RegionName: fmt.Sprintf("Unnamed %d", regionID), + } + } + workspaceAgent.DERPLatency[region.RegionName] = codersdk.DERPRegion{ + Preferred: node.PreferredDERP == regionID, + LatencyMilliseconds: latency * 1000, + } + } + } + + status := dbAgent.Status(agentInactiveDisconnectTimeout) + workspaceAgent.Status = codersdk.WorkspaceAgentStatus(status.Status) + workspaceAgent.FirstConnectedAt = status.FirstConnectedAt + workspaceAgent.LastConnectedAt = status.LastConnectedAt + workspaceAgent.DisconnectedAt = status.DisconnectedAt + + if dbAgent.StartedAt.Valid { + workspaceAgent.StartedAt = &dbAgent.StartedAt.Time + } + if dbAgent.ReadyAt.Valid { + workspaceAgent.ReadyAt = &dbAgent.ReadyAt.Time + } + + switch { + case workspaceAgent.Status != codersdk.WorkspaceAgentConnected && workspaceAgent.LifecycleState == codersdk.WorkspaceAgentLifecycleOff: + workspaceAgent.Health.Reason = "agent is not running" + case workspaceAgent.Status == codersdk.WorkspaceAgentTimeout: + workspaceAgent.Health.Reason = "agent is taking too long to connect" + case workspaceAgent.Status == codersdk.WorkspaceAgentDisconnected: + workspaceAgent.Health.Reason = "agent has lost connection" + // Note: We could also handle codersdk.WorkspaceAgentLifecycleStartTimeout + // here, but it's more of a soft issue, so we don't want to mark the agent + // as unhealthy. + case workspaceAgent.LifecycleState == codersdk.WorkspaceAgentLifecycleStartError: + workspaceAgent.Health.Reason = "agent startup script exited with an error" + case workspaceAgent.LifecycleState.ShuttingDown(): + workspaceAgent.Health.Reason = "agent is shutting down" + default: + workspaceAgent.Health.Healthy = true + } + + return workspaceAgent, nil +} + +func AppSubdomain(dbApp database.WorkspaceApp, agentName, workspaceName, ownerName string) string { + if !dbApp.Subdomain || agentName == "" || ownerName == "" || workspaceName == "" { + return "" + } + + appSlug := dbApp.Slug + if appSlug == "" { + appSlug = dbApp.DisplayName + } + return httpapi.ApplicationURL{ + // We never generate URLs with a prefix. We only allow prefixes when + // parsing URLs from the hostname. Users that want this feature can + // write out their own URLs. + Prefix: "", + AppSlugOrPort: appSlug, + AgentName: agentName, + WorkspaceName: workspaceName, + Username: ownerName, + }.String() +} + +func Apps(dbApps []database.WorkspaceApp, agent database.WorkspaceAgent, ownerName string, workspace database.Workspace) []codersdk.WorkspaceApp { + apps := make([]codersdk.WorkspaceApp, 0) + for _, dbApp := range dbApps { + apps = append(apps, codersdk.WorkspaceApp{ + ID: dbApp.ID, + URL: dbApp.Url.String, + External: dbApp.External, + Slug: dbApp.Slug, + DisplayName: dbApp.DisplayName, + Command: dbApp.Command.String, + Icon: dbApp.Icon, + Subdomain: dbApp.Subdomain, + SubdomainName: AppSubdomain(dbApp, agent.Name, workspace.Name, ownerName), + SharingLevel: codersdk.WorkspaceAppSharingLevel(dbApp.SharingLevel), + Healthcheck: codersdk.Healthcheck{ + URL: dbApp.HealthcheckUrl, + Interval: dbApp.HealthcheckInterval, + Threshold: dbApp.HealthcheckThreshold, + }, + Health: codersdk.WorkspaceAppHealth(dbApp.Health), + }) + } + return apps +} diff --git a/coderd/insights_test.go b/coderd/insights_test.go index 5a027e8823..07461fc267 100644 --- a/coderd/insights_test.go +++ b/coderd/insights_test.go @@ -20,6 +20,7 @@ import ( "cdr.dev/slog" "cdr.dev/slog/sloggers/slogtest" "github.com/coder/coder/v2/agent/agenttest" + agentproto "github.com/coder/coder/v2/agent/proto" "github.com/coder/coder/v2/coderd/batchstats" "github.com/coder/coder/v2/coderd/coderdtest" "github.com/coder/coder/v2/coderd/database" @@ -658,12 +659,12 @@ func TestTemplateInsights_Golden(t *testing.T) { connectionCount = 0 } for createdAt.Before(stat.endedAt) { - err = batcher.Add(createdAt, workspace.agentID, workspace.template.id, workspace.user.(*testUser).sdk.ID, workspace.id, agentsdk.Stats{ + err = batcher.Add(createdAt, workspace.agentID, workspace.template.id, workspace.user.(*testUser).sdk.ID, workspace.id, &agentproto.Stats{ ConnectionCount: connectionCount, - SessionCountVSCode: stat.sessionCountVSCode, - SessionCountJetBrains: stat.sessionCountJetBrains, - SessionCountReconnectingPTY: stat.sessionCountReconnectingPTY, - SessionCountSSH: stat.sessionCountSSH, + SessionCountVscode: stat.sessionCountVSCode, + SessionCountJetbrains: stat.sessionCountJetBrains, + SessionCountReconnectingPty: stat.sessionCountReconnectingPTY, + SessionCountSsh: stat.sessionCountSSH, }) require.NoError(t, err, "want no error inserting agent stats") createdAt = createdAt.Add(30 * time.Second) @@ -1545,12 +1546,12 @@ func TestUserActivityInsights_Golden(t *testing.T) { connectionCount = 0 } for createdAt.Before(stat.endedAt) { - err = batcher.Add(createdAt, workspace.agentID, workspace.template.id, workspace.user.(*testUser).sdk.ID, workspace.id, agentsdk.Stats{ + err = batcher.Add(createdAt, workspace.agentID, workspace.template.id, workspace.user.(*testUser).sdk.ID, workspace.id, &agentproto.Stats{ ConnectionCount: connectionCount, - SessionCountVSCode: stat.sessionCountVSCode, - SessionCountJetBrains: stat.sessionCountJetBrains, - SessionCountReconnectingPTY: stat.sessionCountReconnectingPTY, - SessionCountSSH: stat.sessionCountSSH, + SessionCountVscode: stat.sessionCountVSCode, + SessionCountJetbrains: stat.sessionCountJetBrains, + SessionCountReconnectingPty: stat.sessionCountReconnectingPTY, + SessionCountSsh: stat.sessionCountSSH, }) require.NoError(t, err, "want no error inserting agent stats") createdAt = createdAt.Add(30 * time.Second) diff --git a/coderd/prometheusmetrics/aggregator.go b/coderd/prometheusmetrics/aggregator.go index d3d19bf239..9eb3f08072 100644 --- a/coderd/prometheusmetrics/aggregator.go +++ b/coderd/prometheusmetrics/aggregator.go @@ -10,7 +10,7 @@ import ( "cdr.dev/slog" - "github.com/coder/coder/v2/codersdk/agentsdk" + agentproto "github.com/coder/coder/v2/agent/proto" ) const ( @@ -49,13 +49,13 @@ type updateRequest struct { agentName string templateName string - metrics []agentsdk.AgentMetric + metrics []*agentproto.Stats_Metric timestamp time.Time } type annotatedMetric struct { - agentsdk.AgentMetric + *agentproto.Stats_Metric username string workspaceName string @@ -67,7 +67,7 @@ type annotatedMetric struct { var _ prometheus.Collector = new(MetricsAggregator) -func (am *annotatedMetric) is(req updateRequest, m agentsdk.AgentMetric) bool { +func (am *annotatedMetric) is(req updateRequest, m *agentproto.Stats_Metric) bool { return am.username == req.username && am.workspaceName == req.workspaceName && am.agentName == req.agentName && am.Name == m.Name && slices.Equal(am.Labels, m.Labels) } @@ -152,21 +152,19 @@ func (ma *MetricsAggregator) Run(ctx context.Context) func() { for _, m := range req.metrics { for i, q := range ma.queue { if q.is(req, m) { - ma.queue[i].AgentMetric.Value = m.Value + ma.queue[i].Stats_Metric.Value = m.Value ma.queue[i].expiryDate = req.timestamp.Add(ma.metricsCleanupInterval) continue UpdateLoop } } ma.queue = append(ma.queue, annotatedMetric{ + Stats_Metric: m, username: req.username, workspaceName: req.workspaceName, agentName: req.agentName, templateName: req.templateName, - - AgentMetric: m, - - expiryDate: req.timestamp.Add(ma.metricsCleanupInterval), + expiryDate: req.timestamp.Add(ma.metricsCleanupInterval), }) } @@ -258,7 +256,7 @@ func (ma *MetricsAggregator) Collect(ch chan<- prometheus.Metric) { } } -func (ma *MetricsAggregator) Update(ctx context.Context, labels AgentMetricLabels, metrics []agentsdk.AgentMetric) { +func (ma *MetricsAggregator) Update(ctx context.Context, labels AgentMetricLabels, metrics []*agentproto.Stats_Metric) { select { case ma.updateCh <- updateRequest{ username: labels.Username, @@ -276,11 +274,11 @@ func (ma *MetricsAggregator) Update(ctx context.Context, labels AgentMetricLabel } } -func asPrometheusValueType(metricType agentsdk.AgentMetricType) (prometheus.ValueType, error) { +func asPrometheusValueType(metricType agentproto.Stats_Metric_Type) (prometheus.ValueType, error) { switch metricType { - case agentsdk.AgentMetricTypeGauge: + case agentproto.Stats_Metric_GAUGE: return prometheus.GaugeValue, nil - case agentsdk.AgentMetricTypeCounter: + case agentproto.Stats_Metric_COUNTER: return prometheus.CounterValue, nil default: return -1, xerrors.Errorf("unsupported value type: %s", metricType) diff --git a/coderd/prometheusmetrics/aggregator_test.go b/coderd/prometheusmetrics/aggregator_test.go index ec305b9d44..5f34f47962 100644 --- a/coderd/prometheusmetrics/aggregator_test.go +++ b/coderd/prometheusmetrics/aggregator_test.go @@ -14,8 +14,8 @@ import ( "cdr.dev/slog/sloggers/slogtest" + agentproto "github.com/coder/coder/v2/agent/proto" "github.com/coder/coder/v2/coderd/prometheusmetrics" - "github.com/coder/coder/v2/codersdk/agentsdk" "github.com/coder/coder/v2/cryptorand" "github.com/coder/coder/v2/testutil" ) @@ -48,33 +48,33 @@ func TestUpdateMetrics_MetricsDoNotExpire(t *testing.T) { closeFunc := metricsAggregator.Run(ctx) t.Cleanup(closeFunc) - given1 := []agentsdk.AgentMetric{ - {Name: "a_counter_one", Type: agentsdk.AgentMetricTypeCounter, Value: 1}, - {Name: "b_counter_two", Type: agentsdk.AgentMetricTypeCounter, Value: 2}, - {Name: "c_gauge_three", Type: agentsdk.AgentMetricTypeGauge, Value: 3}, + given1 := []*agentproto.Stats_Metric{ + {Name: "a_counter_one", Type: agentproto.Stats_Metric_COUNTER, Value: 1}, + {Name: "b_counter_two", Type: agentproto.Stats_Metric_COUNTER, Value: 2}, + {Name: "c_gauge_three", Type: agentproto.Stats_Metric_GAUGE, Value: 3}, } - given2 := []agentsdk.AgentMetric{ - {Name: "b_counter_two", Type: agentsdk.AgentMetricTypeCounter, Value: 4}, - {Name: "c_gauge_three", Type: agentsdk.AgentMetricTypeGauge, Value: 5}, - {Name: "c_gauge_three", Type: agentsdk.AgentMetricTypeGauge, Value: 2, Labels: []agentsdk.AgentMetricLabel{ + given2 := []*agentproto.Stats_Metric{ + {Name: "b_counter_two", Type: agentproto.Stats_Metric_COUNTER, Value: 4}, + {Name: "c_gauge_three", Type: agentproto.Stats_Metric_GAUGE, Value: 5}, + {Name: "c_gauge_three", Type: agentproto.Stats_Metric_GAUGE, Value: 2, Labels: []*agentproto.Stats_Metric_Label{ {Name: "foobar", Value: "Foobaz"}, {Name: "hello", Value: "world"}, }}, - {Name: "d_gauge_four", Type: agentsdk.AgentMetricTypeGauge, Value: 6}, + {Name: "d_gauge_four", Type: agentproto.Stats_Metric_GAUGE, Value: 6}, } - commonLabels := []agentsdk.AgentMetricLabel{ + commonLabels := []*agentproto.Stats_Metric_Label{ {Name: "agent_name", Value: testAgentName}, {Name: "username", Value: testUsername}, {Name: "workspace_name", Value: testWorkspaceName}, {Name: "template_name", Value: testTemplateName}, } - expected := []agentsdk.AgentMetric{ - {Name: "a_counter_one", Type: agentsdk.AgentMetricTypeCounter, Value: 1, Labels: commonLabels}, - {Name: "b_counter_two", Type: agentsdk.AgentMetricTypeCounter, Value: 4, Labels: commonLabels}, - {Name: "c_gauge_three", Type: agentsdk.AgentMetricTypeGauge, Value: 5, Labels: commonLabels}, - {Name: "c_gauge_three", Type: agentsdk.AgentMetricTypeGauge, Value: 2, Labels: []agentsdk.AgentMetricLabel{ + expected := []*agentproto.Stats_Metric{ + {Name: "a_counter_one", Type: agentproto.Stats_Metric_COUNTER, Value: 1, Labels: commonLabels}, + {Name: "b_counter_two", Type: agentproto.Stats_Metric_COUNTER, Value: 4, Labels: commonLabels}, + {Name: "c_gauge_three", Type: agentproto.Stats_Metric_GAUGE, Value: 5, Labels: commonLabels}, + {Name: "c_gauge_three", Type: agentproto.Stats_Metric_GAUGE, Value: 2, Labels: []*agentproto.Stats_Metric_Label{ {Name: "agent_name", Value: testAgentName}, {Name: "foobar", Value: "Foobaz"}, {Name: "hello", Value: "world"}, @@ -82,7 +82,7 @@ func TestUpdateMetrics_MetricsDoNotExpire(t *testing.T) { {Name: "workspace_name", Value: testWorkspaceName}, {Name: "template_name", Value: testTemplateName}, }}, - {Name: "d_gauge_four", Type: agentsdk.AgentMetricTypeGauge, Value: 6, Labels: commonLabels}, + {Name: "d_gauge_four", Type: agentproto.Stats_Metric_GAUGE, Value: 6, Labels: commonLabels}, } // when @@ -109,7 +109,7 @@ func TestUpdateMetrics_MetricsDoNotExpire(t *testing.T) { }, testutil.WaitMedium, testutil.IntervalSlow) } -func verifyCollectedMetrics(t *testing.T, expected []agentsdk.AgentMetric, actual []prometheus.Metric) bool { +func verifyCollectedMetrics(t *testing.T, expected []*agentproto.Stats_Metric, actual []prometheus.Metric) bool { if len(expected) != len(actual) { return false } @@ -122,9 +122,9 @@ func verifyCollectedMetrics(t *testing.T, expected []agentsdk.AgentMetric, actua err := actual[i].Write(&d) require.NoError(t, err) - if e.Type == agentsdk.AgentMetricTypeCounter { + if e.Type == agentproto.Stats_Metric_COUNTER { require.Equal(t, e.Value, d.Counter.GetValue()) - } else if e.Type == agentsdk.AgentMetricTypeGauge { + } else if e.Type == agentproto.Stats_Metric_GAUGE { require.Equal(t, e.Value, d.Gauge.GetValue()) } else { require.Failf(t, "unsupported type: %s", string(e.Type)) @@ -140,10 +140,10 @@ func verifyCollectedMetrics(t *testing.T, expected []agentsdk.AgentMetric, actua return true } -func asMetricAgentLabels(dtoLabels []*dto.LabelPair) []agentsdk.AgentMetricLabel { - metricLabels := make([]agentsdk.AgentMetricLabel, 0, len(dtoLabels)) +func asMetricAgentLabels(dtoLabels []*dto.LabelPair) []*agentproto.Stats_Metric_Label { + metricLabels := make([]*agentproto.Stats_Metric_Label, 0, len(dtoLabels)) for _, dtoLabel := range dtoLabels { - metricLabels = append(metricLabels, agentsdk.AgentMetricLabel{ + metricLabels = append(metricLabels, &agentproto.Stats_Metric_Label{ Name: dtoLabel.GetName(), Value: dtoLabel.GetValue(), }) @@ -165,8 +165,8 @@ func TestUpdateMetrics_MetricsExpire(t *testing.T) { closeFunc := metricsAggregator.Run(ctx) t.Cleanup(closeFunc) - given := []agentsdk.AgentMetric{ - {Name: "a_counter_one", Type: agentsdk.AgentMetricTypeCounter, Value: 1}, + given := []*agentproto.Stats_Metric{ + {Name: "a_counter_one", Type: agentproto.Stats_Metric_COUNTER, Value: 1}, } // when @@ -228,7 +228,7 @@ func Benchmark_MetricsAggregator_Run(b *testing.B) { for i := 0; i < b.N; i++ { b.StopTimer() b.Logf("N=%d generating %d metrics", b.N, numMetrics) - metrics := make([]agentsdk.AgentMetric, 0, numMetrics) + metrics := make([]*agentproto.Stats_Metric, 0, numMetrics) for i := 0; i < numMetrics; i++ { metrics = append(metrics, genAgentMetric(b)) } @@ -250,14 +250,14 @@ func Benchmark_MetricsAggregator_Run(b *testing.B) { } } -func genAgentMetric(t testing.TB) agentsdk.AgentMetric { +func genAgentMetric(t testing.TB) *agentproto.Stats_Metric { t.Helper() - var metricType agentsdk.AgentMetricType + var metricType agentproto.Stats_Metric_Type if must(cryptorand.Float64()) >= 0.5 { - metricType = agentsdk.AgentMetricTypeCounter + metricType = agentproto.Stats_Metric_COUNTER } else { - metricType = agentsdk.AgentMetricTypeGauge + metricType = agentproto.Stats_Metric_GAUGE } // Ensure that metric name does not start or end with underscore, as it is not allowed by Prometheus. @@ -265,8 +265,8 @@ func genAgentMetric(t testing.TB) agentsdk.AgentMetric { // Generate random metric value between 0 and 1000. metricValue := must(cryptorand.Float64()) * float64(must(cryptorand.Intn(1000))) - return agentsdk.AgentMetric{ - Name: metricName, Type: metricType, Value: metricValue, Labels: []agentsdk.AgentMetricLabel{}, + return &agentproto.Stats_Metric{ + Name: metricName, Type: metricType, Value: metricValue, Labels: []*agentproto.Stats_Metric_Label{}, } } diff --git a/coderd/provisionerjobs.go b/coderd/provisionerjobs.go index 315fe38593..df832b810e 100644 --- a/coderd/provisionerjobs.go +++ b/coderd/provisionerjobs.go @@ -17,6 +17,7 @@ import ( "cdr.dev/slog" "github.com/coder/coder/v2/coderd/database" + "github.com/coder/coder/v2/coderd/database/db2sdk" "github.com/coder/coder/v2/coderd/database/dbauthz" "github.com/coder/coder/v2/coderd/database/pubsub" "github.com/coder/coder/v2/coderd/httpapi" @@ -185,7 +186,7 @@ func (api *API) provisionerJobResources(rw http.ResponseWriter, r *http.Request, } } - apiAgent, err := convertWorkspaceAgent( + apiAgent, err := db2sdk.WorkspaceAgent( api.DERPMap(), *api.TailnetCoordinator.Load(), agent, convertProvisionedApps(dbApps), convertScripts(dbScripts), convertLogSources(dbLogSources), api.AgentInactiveDisconnectTimeout, api.DeploymentValues.AgentFallbackTroubleshootingURL.String(), ) diff --git a/coderd/workspaceagents.go b/coderd/workspaceagents.go index 25ea30bba1..04428aed28 100644 --- a/coderd/workspaceagents.go +++ b/coderd/workspaceagents.go @@ -30,8 +30,11 @@ import ( "tailscale.com/tailcfg" "cdr.dev/slog" + agentproto "github.com/coder/coder/v2/agent/proto" + "github.com/coder/coder/v2/coderd/agentapi" "github.com/coder/coder/v2/coderd/autobuild" "github.com/coder/coder/v2/coderd/database" + "github.com/coder/coder/v2/coderd/database/db2sdk" "github.com/coder/coder/v2/coderd/database/dbauthz" "github.com/coder/coder/v2/coderd/database/dbtime" "github.com/coder/coder/v2/coderd/externalauth" @@ -124,8 +127,8 @@ func (api *API) workspaceAgent(rw http.ResponseWriter, r *http.Request) { return } - apiAgent, err := convertWorkspaceAgent( - api.DERPMap(), *api.TailnetCoordinator.Load(), workspaceAgent, convertApps(dbApps, workspaceAgent, owner.Username, workspace), convertScripts(scripts), convertLogSources(logSources), api.AgentInactiveDisconnectTimeout, + apiAgent, err := db2sdk.WorkspaceAgent( + api.DERPMap(), *api.TailnetCoordinator.Load(), workspaceAgent, db2sdk.Apps(dbApps, workspaceAgent, owner.Username, workspace), convertScripts(scripts), convertLogSources(logSources), api.AgentInactiveDisconnectTimeout, api.DeploymentValues.AgentFallbackTroubleshootingURL.String(), ) if err != nil { @@ -149,68 +152,24 @@ func (api *API) workspaceAgent(rw http.ResponseWriter, r *http.Request) { func (api *API) workspaceAgentManifest(rw http.ResponseWriter, r *http.Request) { ctx := r.Context() workspaceAgent := httpmw.WorkspaceAgent(r) - apiAgent, err := convertWorkspaceAgent( - api.DERPMap(), *api.TailnetCoordinator.Load(), workspaceAgent, nil, nil, nil, api.AgentInactiveDisconnectTimeout, - api.DeploymentValues.AgentFallbackTroubleshootingURL.String(), - ) - if err != nil { - httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ - Message: "Internal error reading workspace agent.", - Detail: err.Error(), - }) - return + + // As this API becomes deprecated, use the new protobuf API and convert the + // types back to the SDK types. + manifestAPI := &agentapi.ManifestAPI{ + AccessURL: api.AccessURL, + AppHostname: api.AppHostname, + AgentInactiveDisconnectTimeout: api.AgentInactiveDisconnectTimeout, + AgentFallbackTroubleshootingURL: api.DeploymentValues.AgentFallbackTroubleshootingURL.String(), + ExternalAuthConfigs: api.ExternalAuthConfigs, + DisableDirectConnections: api.DeploymentValues.DERP.Config.BlockDirect.Value(), + DerpForceWebSockets: api.DeploymentValues.DERP.Config.ForceWebSockets.Value(), + + AgentFn: func(_ context.Context) (database.WorkspaceAgent, error) { return workspaceAgent, nil }, + Database: api.Database, + DerpMapFn: api.DERPMap, + TailnetCoordinator: &api.TailnetCoordinator, } - - var ( - dbApps []database.WorkspaceApp - scripts []database.WorkspaceAgentScript - metadata []database.WorkspaceAgentMetadatum - resource database.WorkspaceResource - build database.WorkspaceBuild - workspace database.Workspace - owner database.User - ) - - var eg errgroup.Group - eg.Go(func() (err error) { - dbApps, err = api.Database.GetWorkspaceAppsByAgentID(ctx, workspaceAgent.ID) - if err != nil && !xerrors.Is(err, sql.ErrNoRows) { - return err - } - return nil - }) - eg.Go(func() (err error) { - // nolint:gocritic // This is necessary to fetch agent scripts! - scripts, err = api.Database.GetWorkspaceAgentScriptsByAgentIDs(dbauthz.AsSystemRestricted(ctx), []uuid.UUID{workspaceAgent.ID}) - return err - }) - eg.Go(func() (err error) { - metadata, err = api.Database.GetWorkspaceAgentMetadata(ctx, database.GetWorkspaceAgentMetadataParams{ - WorkspaceAgentID: workspaceAgent.ID, - Keys: nil, - }) - return err - }) - eg.Go(func() (err error) { - resource, err = api.Database.GetWorkspaceResourceByID(ctx, workspaceAgent.ResourceID) - if err != nil { - return xerrors.Errorf("getting resource by id: %w", err) - } - build, err = api.Database.GetWorkspaceBuildByJobID(ctx, resource.JobID) - if err != nil { - return xerrors.Errorf("getting workspace build by job id: %w", err) - } - workspace, err = api.Database.GetWorkspaceByID(ctx, build.WorkspaceID) - if err != nil { - return xerrors.Errorf("getting workspace by id: %w", err) - } - owner, err = api.Database.GetUserByID(ctx, workspace.OwnerID) - if err != nil { - return xerrors.Errorf("getting workspace owner by id: %w", err) - } - return err - }) - err = eg.Wait() + manifest, err := manifestAPI.GetManifest(ctx, &agentproto.GetManifestRequest{}) if err != nil { httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching workspace agent manifest.", @@ -219,42 +178,56 @@ func (api *API) workspaceAgentManifest(rw http.ResponseWriter, r *http.Request) return } - appHost := httpapi.ApplicationURL{ - AppSlugOrPort: "{{port}}", - AgentName: workspaceAgent.Name, - WorkspaceName: workspace.Name, - Username: owner.Username, - } - vscodeProxyURI := api.AccessURL.Scheme + "://" + strings.ReplaceAll(api.AppHostname, "*", appHost.String()) - if api.AppHostname == "" { - vscodeProxyURI += api.AccessURL.Hostname() - } - if api.AccessURL.Port() != "" { - vscodeProxyURI += fmt.Sprintf(":%s", api.AccessURL.Port()) + apps, err := agentproto.SDKAppsFromProto(manifest.Apps) + if err != nil { + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ + Message: "Internal error converting workspace agent apps.", + Detail: err.Error(), + }) + return } - gitAuthConfigs := 0 - for _, cfg := range api.ExternalAuthConfigs { - if codersdk.EnhancedExternalAuthProvider(cfg.Type).Git() { - gitAuthConfigs++ - } + scripts, err := agentproto.SDKAgentScriptsFromProto(manifest.Scripts) + if err != nil { + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ + Message: "Internal error converting workspace agent scripts.", + Detail: err.Error(), + }) + return + } + + agentID, err := uuid.FromBytes(manifest.AgentId) + if err != nil { + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ + Message: "Internal error converting workspace agent ID.", + Detail: err.Error(), + }) + return + } + workspaceID, err := uuid.FromBytes(manifest.WorkspaceId) + if err != nil { + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ + Message: "Internal error converting workspace ID.", + Detail: err.Error(), + }) + return } httpapi.Write(ctx, rw, http.StatusOK, agentsdk.Manifest{ - AgentID: apiAgent.ID, - OwnerName: owner.Username, - WorkspaceID: workspace.ID, - Apps: convertApps(dbApps, workspaceAgent, owner.Username, workspace), - Scripts: convertScripts(scripts), - DERPMap: api.DERPMap(), - DERPForceWebSockets: api.DeploymentValues.DERP.Config.ForceWebSockets.Value(), - GitAuthConfigs: gitAuthConfigs, - EnvironmentVariables: apiAgent.EnvironmentVariables, - Directory: apiAgent.Directory, - VSCodePortProxyURI: vscodeProxyURI, - MOTDFile: workspaceAgent.MOTDFile, - DisableDirectConnections: api.DeploymentValues.DERP.Config.BlockDirect.Value(), - Metadata: convertWorkspaceAgentMetadataDesc(metadata), + AgentID: agentID, + OwnerName: manifest.OwnerUsername, + WorkspaceID: workspaceID, + Apps: apps, + Scripts: scripts, + DERPMap: tailnet.DERPMapFromProto(manifest.DerpMap), + DERPForceWebSockets: manifest.DerpForceWebsockets, + GitAuthConfigs: int(manifest.GitAuthConfigs), + EnvironmentVariables: manifest.EnvironmentVariables, + Directory: manifest.Directory, + VSCodePortProxyURI: manifest.VsCodePortProxyUri, + MOTDFile: manifest.MotdPath, + DisableDirectConnections: manifest.DisableDirectConnections, + Metadata: agentproto.SDKAgentMetadataDescriptionsFromProto(manifest.Metadata), }) } @@ -273,7 +246,7 @@ const AgentAPIVersionREST = "1.0" func (api *API) postWorkspaceAgentStartup(rw http.ResponseWriter, r *http.Request) { ctx := r.Context() workspaceAgent := httpmw.WorkspaceAgent(r) - apiAgent, err := convertWorkspaceAgent( + apiAgent, err := db2sdk.WorkspaceAgent( api.DERPMap(), *api.TailnetCoordinator.Load(), workspaceAgent, nil, nil, nil, api.AgentInactiveDisconnectTimeout, api.DeploymentValues.AgentFallbackTroubleshootingURL.String(), ) @@ -789,7 +762,7 @@ func (api *API) workspaceAgentListeningPorts(rw http.ResponseWriter, r *http.Req ctx := r.Context() workspaceAgent := httpmw.WorkspaceAgentParam(r) - apiAgent, err := convertWorkspaceAgent( + apiAgent, err := db2sdk.WorkspaceAgent( api.DERPMap(), *api.TailnetCoordinator.Load(), workspaceAgent, nil, nil, nil, api.AgentInactiveDisconnectTimeout, api.DeploymentValues.AgentFallbackTroubleshootingURL.String(), ) @@ -1432,50 +1405,7 @@ func (api *API) workspaceAgentClientCoordinate(rw http.ResponseWriter, r *http.R // convertProvisionedApps converts applications that are in the middle of provisioning process. // It means that they may not have an agent or workspace assigned (dry-run job). func convertProvisionedApps(dbApps []database.WorkspaceApp) []codersdk.WorkspaceApp { - return convertApps(dbApps, database.WorkspaceAgent{}, "", database.Workspace{}) -} - -func convertApps(dbApps []database.WorkspaceApp, agent database.WorkspaceAgent, ownerName string, workspace database.Workspace) []codersdk.WorkspaceApp { - apps := make([]codersdk.WorkspaceApp, 0) - for _, dbApp := range dbApps { - var subdomainName string - if dbApp.Subdomain && agent.Name != "" && ownerName != "" && workspace.Name != "" { - appSlug := dbApp.Slug - if appSlug == "" { - appSlug = dbApp.DisplayName - } - subdomainName = httpapi.ApplicationURL{ - // We never generate URLs with a prefix. We only allow prefixes - // when parsing URLs from the hostname. Users that want this - // feature can write out their own URLs. - Prefix: "", - AppSlugOrPort: appSlug, - AgentName: agent.Name, - WorkspaceName: workspace.Name, - Username: ownerName, - }.String() - } - - apps = append(apps, codersdk.WorkspaceApp{ - ID: dbApp.ID, - URL: dbApp.Url.String, - External: dbApp.External, - Slug: dbApp.Slug, - DisplayName: dbApp.DisplayName, - Command: dbApp.Command.String, - Icon: dbApp.Icon, - Subdomain: dbApp.Subdomain, - SubdomainName: subdomainName, - SharingLevel: codersdk.WorkspaceAppSharingLevel(dbApp.SharingLevel), - Healthcheck: codersdk.Healthcheck{ - URL: dbApp.HealthcheckUrl, - Interval: dbApp.HealthcheckInterval, - Threshold: dbApp.HealthcheckThreshold, - }, - Health: codersdk.WorkspaceAppHealth(dbApp.Health), - }) - } - return apps + return db2sdk.Apps(dbApps, database.WorkspaceAgent{}, "", database.Workspace{}) } func convertLogSources(dbLogSources []database.WorkspaceAgentLogSource) []codersdk.WorkspaceAgentLogSource { @@ -1509,149 +1439,6 @@ func convertScripts(dbScripts []database.WorkspaceAgentScript) []codersdk.Worksp return scripts } -func convertWorkspaceAgentMetadataDesc(mds []database.WorkspaceAgentMetadatum) []codersdk.WorkspaceAgentMetadataDescription { - metadata := make([]codersdk.WorkspaceAgentMetadataDescription, 0) - for _, datum := range mds { - metadata = append(metadata, codersdk.WorkspaceAgentMetadataDescription{ - DisplayName: datum.DisplayName, - Key: datum.Key, - Script: datum.Script, - Interval: datum.Interval, - Timeout: datum.Timeout, - }) - } - return metadata -} - -func convertWorkspaceAgent(derpMap *tailcfg.DERPMap, coordinator tailnet.CoordinatorV1, - dbAgent database.WorkspaceAgent, apps []codersdk.WorkspaceApp, scripts []codersdk.WorkspaceAgentScript, logSources []codersdk.WorkspaceAgentLogSource, - agentInactiveDisconnectTimeout time.Duration, agentFallbackTroubleshootingURL string, -) (codersdk.WorkspaceAgent, error) { - var envs map[string]string - if dbAgent.EnvironmentVariables.Valid { - err := json.Unmarshal(dbAgent.EnvironmentVariables.RawMessage, &envs) - if err != nil { - return codersdk.WorkspaceAgent{}, xerrors.Errorf("unmarshal env vars: %w", err) - } - } - troubleshootingURL := agentFallbackTroubleshootingURL - if dbAgent.TroubleshootingURL != "" { - troubleshootingURL = dbAgent.TroubleshootingURL - } - subsystems := make([]codersdk.AgentSubsystem, len(dbAgent.Subsystems)) - for i, subsystem := range dbAgent.Subsystems { - subsystems[i] = codersdk.AgentSubsystem(subsystem) - } - - legacyStartupScriptBehavior := codersdk.WorkspaceAgentStartupScriptBehaviorNonBlocking - for _, script := range scripts { - if !script.RunOnStart { - continue - } - if !script.StartBlocksLogin { - continue - } - legacyStartupScriptBehavior = codersdk.WorkspaceAgentStartupScriptBehaviorBlocking - } - - workspaceAgent := codersdk.WorkspaceAgent{ - ID: dbAgent.ID, - CreatedAt: dbAgent.CreatedAt, - UpdatedAt: dbAgent.UpdatedAt, - ResourceID: dbAgent.ResourceID, - InstanceID: dbAgent.AuthInstanceID.String, - Name: dbAgent.Name, - Architecture: dbAgent.Architecture, - OperatingSystem: dbAgent.OperatingSystem, - Scripts: scripts, - StartupScriptBehavior: legacyStartupScriptBehavior, - LogsLength: dbAgent.LogsLength, - LogsOverflowed: dbAgent.LogsOverflowed, - LogSources: logSources, - Version: dbAgent.Version, - APIVersion: dbAgent.APIVersion, - EnvironmentVariables: envs, - Directory: dbAgent.Directory, - ExpandedDirectory: dbAgent.ExpandedDirectory, - Apps: apps, - ConnectionTimeoutSeconds: dbAgent.ConnectionTimeoutSeconds, - TroubleshootingURL: troubleshootingURL, - LifecycleState: codersdk.WorkspaceAgentLifecycle(dbAgent.LifecycleState), - Subsystems: subsystems, - DisplayApps: convertDisplayApps(dbAgent.DisplayApps), - } - node := coordinator.Node(dbAgent.ID) - if node != nil { - workspaceAgent.DERPLatency = map[string]codersdk.DERPRegion{} - for rawRegion, latency := range node.DERPLatency { - regionParts := strings.SplitN(rawRegion, "-", 2) - regionID, err := strconv.Atoi(regionParts[0]) - if err != nil { - return codersdk.WorkspaceAgent{}, xerrors.Errorf("convert derp region id %q: %w", rawRegion, err) - } - region, found := derpMap.Regions[regionID] - if !found { - // It's possible that a workspace agent is using an old DERPMap - // and reports regions that do not exist. If that's the case, - // report the region as unknown! - region = &tailcfg.DERPRegion{ - RegionID: regionID, - RegionName: fmt.Sprintf("Unnamed %d", regionID), - } - } - workspaceAgent.DERPLatency[region.RegionName] = codersdk.DERPRegion{ - Preferred: node.PreferredDERP == regionID, - LatencyMilliseconds: latency * 1000, - } - } - } - - status := dbAgent.Status(agentInactiveDisconnectTimeout) - workspaceAgent.Status = codersdk.WorkspaceAgentStatus(status.Status) - workspaceAgent.FirstConnectedAt = status.FirstConnectedAt - workspaceAgent.LastConnectedAt = status.LastConnectedAt - workspaceAgent.DisconnectedAt = status.DisconnectedAt - - if dbAgent.StartedAt.Valid { - workspaceAgent.StartedAt = &dbAgent.StartedAt.Time - } - if dbAgent.ReadyAt.Valid { - workspaceAgent.ReadyAt = &dbAgent.ReadyAt.Time - } - - switch { - case workspaceAgent.Status != codersdk.WorkspaceAgentConnected && workspaceAgent.LifecycleState == codersdk.WorkspaceAgentLifecycleOff: - workspaceAgent.Health.Reason = "agent is not running" - case workspaceAgent.Status == codersdk.WorkspaceAgentTimeout: - workspaceAgent.Health.Reason = "agent is taking too long to connect" - case workspaceAgent.Status == codersdk.WorkspaceAgentDisconnected: - workspaceAgent.Health.Reason = "agent has lost connection" - // Note: We could also handle codersdk.WorkspaceAgentLifecycleStartTimeout - // here, but it's more of a soft issue, so we don't want to mark the agent - // as unhealthy. - case workspaceAgent.LifecycleState == codersdk.WorkspaceAgentLifecycleStartError: - workspaceAgent.Health.Reason = "agent startup script exited with an error" - case workspaceAgent.LifecycleState.ShuttingDown(): - workspaceAgent.Health.Reason = "agent is shutting down" - default: - workspaceAgent.Health.Healthy = true - } - - return workspaceAgent, nil -} - -func convertDisplayApps(apps []database.DisplayApp) []codersdk.DisplayApp { - dapps := make([]codersdk.DisplayApp, 0, len(apps)) - for _, app := range apps { - switch codersdk.DisplayApp(app) { - case codersdk.DisplayAppVSCodeDesktop, codersdk.DisplayAppVSCodeInsiders, codersdk.DisplayAppPortForward, codersdk.DisplayAppWebTerminal, codersdk.DisplayAppSSH: - dapps = append(dapps, codersdk.DisplayApp(app)) - } - } - - return dapps -} - // @Summary Submit workspace agent stats // @ID submit-workspace-agent-stats // @Security CoderSessionToken @@ -1713,14 +1500,51 @@ func (api *API) workspaceAgentReportStats(rw http.ResponseWriter, r *http.Reques } } } - activityBumpWorkspace(ctx, api.Logger.Named("activity_bump"), api.Database, workspace.ID, nextAutostart) + agentapi.ActivityBumpWorkspace(ctx, api.Logger.Named("activity_bump"), api.Database, workspace.ID, nextAutostart) } now := dbtime.Now() + protoStats := &agentproto.Stats{ + ConnectionsByProto: req.ConnectionsByProto, + ConnectionCount: req.ConnectionCount, + ConnectionMedianLatencyMs: req.ConnectionMedianLatencyMS, + RxPackets: req.RxPackets, + RxBytes: req.RxBytes, + TxPackets: req.TxPackets, + TxBytes: req.TxBytes, + SessionCountVscode: req.SessionCountVSCode, + SessionCountJetbrains: req.SessionCountJetBrains, + SessionCountReconnectingPty: req.SessionCountReconnectingPTY, + SessionCountSsh: req.SessionCountSSH, + Metrics: make([]*agentproto.Stats_Metric, len(req.Metrics)), + } + for i, metric := range req.Metrics { + metricType := agentproto.Stats_Metric_TYPE_UNSPECIFIED + switch metric.Type { + case agentsdk.AgentMetricTypeCounter: + metricType = agentproto.Stats_Metric_COUNTER + case agentsdk.AgentMetricTypeGauge: + metricType = agentproto.Stats_Metric_GAUGE + } + + protoStats.Metrics[i] = &agentproto.Stats_Metric{ + Name: metric.Name, + Type: metricType, + Value: metric.Value, + Labels: make([]*agentproto.Stats_Metric_Label, len(metric.Labels)), + } + for j, label := range metric.Labels { + protoStats.Metrics[i].Labels[j] = &agentproto.Stats_Metric_Label{ + Name: label.Name, + Value: label.Value, + } + } + } var errGroup errgroup.Group errGroup.Go(func() error { - if err := api.statsBatcher.Add(time.Now(), workspaceAgent.ID, workspace.TemplateID, workspace.OwnerID, workspace.ID, req); err != nil { + err := api.statsBatcher.Add(time.Now(), workspaceAgent.ID, workspace.TemplateID, workspace.OwnerID, workspace.ID, protoStats) + if err != nil { api.Logger.Error(ctx, "failed to add stats to batcher", slog.Error(err)) return xerrors.Errorf("can't insert workspace agent stat: %w", err) } @@ -1750,7 +1574,7 @@ func (api *API) workspaceAgentReportStats(rw http.ResponseWriter, r *http.Reques WorkspaceName: workspace.Name, AgentName: workspaceAgent.Name, TemplateName: row.TemplateName, - }, req.Metrics) + }, protoStats.Metrics) return nil }) } @@ -1854,7 +1678,7 @@ func (api *API) workspaceAgentUpdateMetadata(ctx context.Context, workspaceAgent ) } - payload, err := json.Marshal(workspaceAgentMetadataChannelPayload{ + payload, err := json.Marshal(agentapi.WorkspaceAgentMetadataChannelPayload{ CollectedAt: collectedAt, Keys: datum.Key, }) @@ -1867,7 +1691,7 @@ func (api *API) workspaceAgentUpdateMetadata(ctx context.Context, workspaceAgent return err } - err = api.Pubsub.Publish(watchWorkspaceAgentMetadataChannel(workspaceAgent.ID), payload) + err = api.Pubsub.Publish(agentapi.WatchWorkspaceAgentMetadataChannel(workspaceAgent.ID), payload) if err != nil { return err } @@ -1896,13 +1720,13 @@ func (api *API) watchWorkspaceAgentMetadata(rw http.ResponseWriter, r *http.Requ // Send metadata on updates, we must ensure subscription before sending // initial metadata to guarantee that events in-between are not missed. - update := make(chan workspaceAgentMetadataChannelPayload, 1) - cancelSub, err := api.Pubsub.Subscribe(watchWorkspaceAgentMetadataChannel(workspaceAgent.ID), func(_ context.Context, byt []byte) { + update := make(chan agentapi.WorkspaceAgentMetadataChannelPayload, 1) + cancelSub, err := api.Pubsub.Subscribe(agentapi.WatchWorkspaceAgentMetadataChannel(workspaceAgent.ID), func(_ context.Context, byt []byte) { if ctx.Err() != nil { return } - var payload workspaceAgentMetadataChannelPayload + var payload agentapi.WorkspaceAgentMetadataChannelPayload err := json.Unmarshal(byt, &payload) if err != nil { log.Error(ctx, "failed to unmarshal pubsub message", slog.Error(err)) @@ -2104,15 +1928,6 @@ func convertWorkspaceAgentMetadata(db []database.WorkspaceAgentMetadatum) []code return result } -type workspaceAgentMetadataChannelPayload struct { - CollectedAt time.Time `json:"collected_at"` - Keys []string `json:"keys"` -} - -func watchWorkspaceAgentMetadataChannel(id uuid.UUID) string { - return "workspace_agent_metadata:" + id.String() -} - // @Summary Submit workspace agent lifecycle state // @ID submit-workspace-agent-lifecycle-state // @Security CoderSessionToken diff --git a/coderd/workspaceagentsrpc.go b/coderd/workspaceagentsrpc.go new file mode 100644 index 0000000000..9b4987867e --- /dev/null +++ b/coderd/workspaceagentsrpc.go @@ -0,0 +1,344 @@ +package coderd + +import ( + "context" + "database/sql" + "net/http" + "runtime/pprof" + "sync/atomic" + "time" + + "github.com/google/uuid" + "github.com/hashicorp/yamux" + "golang.org/x/xerrors" + "nhooyr.io/websocket" + + "cdr.dev/slog" + "github.com/coder/coder/v2/coderd/agentapi" + "github.com/coder/coder/v2/coderd/database" + "github.com/coder/coder/v2/coderd/database/dbauthz" + "github.com/coder/coder/v2/coderd/database/dbtime" + "github.com/coder/coder/v2/coderd/httpapi" + "github.com/coder/coder/v2/coderd/httpmw" + "github.com/coder/coder/v2/coderd/util/ptr" + "github.com/coder/coder/v2/codersdk" +) + +// @Summary Workspace agent RPC API +// @ID workspace-agent-rpc-api +// @Security CoderSessionToken +// @Tags Agents +// @Success 101 +// @Router /workspaceagents/me/rpc [get] +// @x-apidocgen {"skip": true} +func (api *API) workspaceAgentRPC(rw http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + api.WebsocketWaitMutex.Lock() + api.WebsocketWaitGroup.Add(1) + api.WebsocketWaitMutex.Unlock() + defer api.WebsocketWaitGroup.Done() + workspaceAgent := httpmw.WorkspaceAgent(r) + + ensureLatestBuildFn, build, ok := ensureLatestBuild(ctx, api.Database, api.Logger, rw, workspaceAgent) + if !ok { + return + } + + workspace, err := api.Database.GetWorkspaceByID(ctx, build.WorkspaceID) + if err != nil { + httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ + Message: "Internal error fetching workspace.", + Detail: err.Error(), + }) + return + } + + owner, err := api.Database.GetUserByID(ctx, workspace.OwnerID) + if err != nil { + httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ + Message: "Internal error fetching user.", + Detail: err.Error(), + }) + return + } + + conn, err := websocket.Accept(rw, r, nil) + if err != nil { + httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ + Message: "Failed to accept websocket.", + Detail: err.Error(), + }) + return + } + + ctx, wsNetConn := websocketNetConn(ctx, conn, websocket.MessageBinary) + defer wsNetConn.Close() + + mux, err := yamux.Server(wsNetConn, nil) + if err != nil { + httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ + Message: "Failed to start yamux over websocket.", + Detail: err.Error(), + }) + return + } + defer mux.Close() + + api.Logger.Debug(ctx, "accepting agent RPC connection", + slog.F("owner", owner.Username), + slog.F("workspace", workspace.Name), + slog.F("name", workspaceAgent.Name), + ) + api.Logger.Debug(ctx, "accepting agent details", slog.F("agent", workspaceAgent)) + + defer conn.Close(websocket.StatusNormalClosure, "") + + pingFn, ok := api.agentConnectionUpdate(ctx, workspaceAgent, build.WorkspaceID, conn) + if !ok { + return + } + + agentAPI := agentapi.New(agentapi.Options{ + AgentID: workspaceAgent.ID, + + Ctx: api.ctx, + Log: api.Logger, + Database: api.Database, + Pubsub: api.Pubsub, + DerpMapFn: api.DERPMap, + TailnetCoordinator: &api.TailnetCoordinator, + TemplateScheduleStore: api.TemplateScheduleStore, + StatsBatcher: api.statsBatcher, + PublishWorkspaceUpdateFn: api.publishWorkspaceUpdate, + PublishWorkspaceAgentLogsUpdateFn: api.publishWorkspaceAgentLogsUpdate, + + AccessURL: api.AccessURL, + AppHostname: api.AppHostname, + AgentInactiveDisconnectTimeout: api.AgentInactiveDisconnectTimeout, + AgentFallbackTroubleshootingURL: api.DeploymentValues.AgentFallbackTroubleshootingURL.String(), + AgentStatsRefreshInterval: api.AgentStatsRefreshInterval, + DisableDirectConnections: api.DeploymentValues.DERP.Config.BlockDirect.Value(), + DerpForceWebSockets: api.DeploymentValues.DERP.Config.ForceWebSockets.Value(), + DerpMapUpdateFrequency: api.Options.DERPMapUpdateFrequency, + ExternalAuthConfigs: api.ExternalAuthConfigs, + + // Optional: + WorkspaceID: build.WorkspaceID, // saves the extra lookup later + UpdateAgentMetricsFn: api.UpdateAgentMetrics, + }) + + closeCtx, closeCtxCancel := context.WithCancel(ctx) + go func() { + defer closeCtxCancel() + err := agentAPI.Serve(ctx, mux) + if err != nil { + api.Logger.Warn(ctx, "workspace agent RPC listen error", slog.Error(err)) + _ = conn.Close(websocket.StatusInternalError, err.Error()) + return + } + }() + + pingFn(closeCtx, ensureLatestBuildFn) +} + +func ensureLatestBuild(ctx context.Context, db database.Store, logger slog.Logger, rw http.ResponseWriter, workspaceAgent database.WorkspaceAgent) (func() error, database.WorkspaceBuild, bool) { + resource, err := db.GetWorkspaceResourceByID(ctx, workspaceAgent.ResourceID) + if err != nil { + httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ + Message: "Internal error fetching workspace agent resource.", + Detail: err.Error(), + }) + return nil, database.WorkspaceBuild{}, false + } + + build, err := db.GetWorkspaceBuildByJobID(ctx, resource.JobID) + if err != nil { + httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ + Message: "Internal error fetching workspace build job.", + Detail: err.Error(), + }) + return nil, database.WorkspaceBuild{}, false + } + + // Ensure the resource is still valid! + // We only accept agents for resources on the latest build. + ensureLatestBuild := func() error { + latestBuild, err := db.GetLatestWorkspaceBuildByWorkspaceID(ctx, build.WorkspaceID) + if err != nil { + return err + } + if build.ID != latestBuild.ID { + return xerrors.New("build is outdated") + } + return nil + } + + err = ensureLatestBuild() + if err != nil { + logger.Debug(ctx, "agent tried to connect from non-latest build", + slog.F("resource", resource), + slog.F("agent", workspaceAgent), + ) + httpapi.Write(ctx, rw, http.StatusForbidden, codersdk.Response{ + Message: "Agent trying to connect from non-latest build.", + Detail: err.Error(), + }) + return nil, database.WorkspaceBuild{}, false + } + + return ensureLatestBuild, build, true +} + +func (api *API) agentConnectionUpdate(ctx context.Context, workspaceAgent database.WorkspaceAgent, workspaceID uuid.UUID, conn *websocket.Conn) (func(closeCtx context.Context, ensureLatestBuildFn func() error), bool) { + // We use a custom heartbeat routine here instead of `httpapi.Heartbeat` + // because we want to log the agent's last ping time. + var lastPing atomic.Pointer[time.Time] + lastPing.Store(ptr.Ref(time.Now())) // Since the agent initiated the request, assume it's alive. + + go pprof.Do(ctx, pprof.Labels("agent", workspaceAgent.ID.String()), func(ctx context.Context) { + // TODO(mafredri): Is this too frequent? Use separate ping disconnect timeout? + t := time.NewTicker(api.AgentConnectionUpdateFrequency) + defer t.Stop() + + for { + select { + case <-t.C: + case <-ctx.Done(): + return + } + + // We don't need a context that times out here because the ping will + // eventually go through. If the context times out, then other + // websocket read operations will receive an error, obfuscating the + // actual problem. + err := conn.Ping(ctx) + if err != nil { + return + } + lastPing.Store(ptr.Ref(time.Now())) + } + }) + + firstConnectedAt := workspaceAgent.FirstConnectedAt + if !firstConnectedAt.Valid { + firstConnectedAt = sql.NullTime{ + Time: dbtime.Now(), + Valid: true, + } + } + lastConnectedAt := sql.NullTime{ + Time: dbtime.Now(), + Valid: true, + } + disconnectedAt := workspaceAgent.DisconnectedAt + updateConnectionTimes := func(ctx context.Context) error { + //nolint:gocritic // We only update ourself. + err := api.Database.UpdateWorkspaceAgentConnectionByID(dbauthz.AsSystemRestricted(ctx), database.UpdateWorkspaceAgentConnectionByIDParams{ + ID: workspaceAgent.ID, + FirstConnectedAt: firstConnectedAt, + LastConnectedAt: lastConnectedAt, + DisconnectedAt: disconnectedAt, + UpdatedAt: dbtime.Now(), + LastConnectedReplicaID: uuid.NullUUID{ + UUID: api.ID, + Valid: true, + }, + }) + if err != nil { + return err + } + return nil + } + + defer func() { + // If connection closed then context will be canceled, try to + // ensure our final update is sent. By waiting at most the agent + // inactive disconnect timeout we ensure that we don't block but + // also guarantee that the agent will be considered disconnected + // by normal status check. + // + // Use a system context as the agent has disconnected and that token + // may no longer be valid. + //nolint:gocritic + ctx, cancel := context.WithTimeout(dbauthz.AsSystemRestricted(api.ctx), api.AgentInactiveDisconnectTimeout) + defer cancel() + + // Only update timestamp if the disconnect is new. + if !disconnectedAt.Valid { + disconnectedAt = sql.NullTime{ + Time: dbtime.Now(), + Valid: true, + } + } + err := updateConnectionTimes(ctx) + if err != nil { + // This is a bug with unit tests that cancel the app context and + // cause this error log to be generated. We should fix the unit tests + // as this is a valid log. + // + // The pq error occurs when the server is shutting down. + if !xerrors.Is(err, context.Canceled) && !database.IsQueryCanceledError(err) { + api.Logger.Error(ctx, "failed to update agent disconnect time", + slog.Error(err), + slog.F("workspace_id", workspaceID), + ) + } + } + api.publishWorkspaceUpdate(ctx, workspaceID) + }() + + err := updateConnectionTimes(ctx) + if err != nil { + _ = conn.Close(websocket.StatusGoingAway, err.Error()) + return nil, false + } + api.publishWorkspaceUpdate(ctx, workspaceID) + + return func(closeCtx context.Context, ensureLatestBuildFn func() error) { + ticker := time.NewTicker(api.AgentConnectionUpdateFrequency) + defer ticker.Stop() + for { + select { + case <-closeCtx.Done(): + return + case <-ticker.C: + } + + lastPing := *lastPing.Load() + + var connectionStatusChanged bool + if time.Since(lastPing) > api.AgentInactiveDisconnectTimeout { + if !disconnectedAt.Valid { + connectionStatusChanged = true + disconnectedAt = sql.NullTime{ + Time: dbtime.Now(), + Valid: true, + } + } + } else { + connectionStatusChanged = disconnectedAt.Valid + // TODO(mafredri): Should we update it here or allow lastConnectedAt to shadow it? + disconnectedAt = sql.NullTime{} + lastConnectedAt = sql.NullTime{ + Time: dbtime.Now(), + Valid: true, + } + } + err = updateConnectionTimes(ctx) + if err != nil { + _ = conn.Close(websocket.StatusGoingAway, err.Error()) + return + } + if connectionStatusChanged { + api.publishWorkspaceUpdate(ctx, workspaceID) + } + err := ensureLatestBuildFn() + if err != nil { + // Disconnect agents that are no longer valid. + _ = conn.Close(websocket.StatusGoingAway, "") + return + } + } + }, true +} diff --git a/coderd/workspacebuilds.go b/coderd/workspacebuilds.go index bfc083c9a5..45813b79eb 100644 --- a/coderd/workspacebuilds.go +++ b/coderd/workspacebuilds.go @@ -908,8 +908,8 @@ func (api *API) convertWorkspaceBuild( apps := appsByAgentID[agent.ID] scripts := scriptsByAgentID[agent.ID] logSources := logSourcesByAgentID[agent.ID] - apiAgent, err := convertWorkspaceAgent( - api.DERPMap(), *api.TailnetCoordinator.Load(), agent, convertApps(apps, agent, ownerName, workspace), convertScripts(scripts), convertLogSources(logSources), api.AgentInactiveDisconnectTimeout, + apiAgent, err := db2sdk.WorkspaceAgent( + api.DERPMap(), *api.TailnetCoordinator.Load(), agent, db2sdk.Apps(apps, agent, ownerName, workspace), convertScripts(scripts), convertLogSources(logSources), api.AgentInactiveDisconnectTimeout, api.DeploymentValues.AgentFallbackTroubleshootingURL.String(), ) if err != nil { diff --git a/codersdk/agentsdk/rpc.go b/codersdk/agentsdk/rpc.go new file mode 100644 index 0000000000..ca53ff9626 --- /dev/null +++ b/codersdk/agentsdk/rpc.go @@ -0,0 +1,68 @@ +package agentsdk + +import ( + "context" + "io" + "net/http" + "net/http/cookiejar" + + "github.com/hashicorp/yamux" + "golang.org/x/xerrors" + "nhooyr.io/websocket" + + agentproto "github.com/coder/coder/v2/agent/proto" + "github.com/coder/coder/v2/codersdk" + "github.com/coder/coder/v2/codersdk/drpc" +) + +func (c *Client) RPC(ctx context.Context) (agentproto.DRPCAgentClient, error) { + rpcURL, err := c.SDK.URL.Parse("/api/v2/workspaceagents/me/rpc") + if err != nil { + return nil, xerrors.Errorf("parse url: %w", err) + } + jar, err := cookiejar.New(nil) + if err != nil { + return nil, xerrors.Errorf("create cookie jar: %w", err) + } + jar.SetCookies(rpcURL, []*http.Cookie{{ + Name: codersdk.SessionTokenCookie, + Value: c.SDK.SessionToken(), + }}) + httpClient := &http.Client{ + Jar: jar, + Transport: c.SDK.HTTPClient.Transport, + } + // nolint:bodyclose + conn, res, err := websocket.Dial(ctx, rpcURL.String(), &websocket.DialOptions{ + HTTPClient: httpClient, + }) + if err != nil { + if res == nil { + return nil, err + } + return nil, codersdk.ReadBodyAsError(res) + } + + ctx, cancelFunc := context.WithCancel(ctx) + ctx, wsNetConn := websocketNetConn(ctx, conn, websocket.MessageBinary) + pingClosed := pingWebSocket(ctx, c.SDK.Logger(), conn, "RPC") + + nconn := &closeNetConn{ + Conn: wsNetConn, + closeFunc: func() { + cancelFunc() + _ = conn.Close(websocket.StatusGoingAway, "Listen closed") + <-pingClosed + }, + } + + config := yamux.DefaultConfig() + config.LogOutput = io.Discard + mux, err := yamux.Client(nconn, config) + if err != nil { + return nil, xerrors.Errorf("create yamux client: %w", err) + } + + dconn := drpc.MultiplexedConn(mux) + return agentproto.NewDRPCAgentClient(dconn), nil +} diff --git a/codersdk/workspaceapps.go b/codersdk/workspaceapps.go index 9c8d89b42f..e253c7e533 100644 --- a/codersdk/workspaceapps.go +++ b/codersdk/workspaceapps.go @@ -13,6 +13,13 @@ const ( WorkspaceAppHealthUnhealthy WorkspaceAppHealth = "unhealthy" ) +var MapWorkspaceAppHealths = map[WorkspaceAppHealth]struct{}{ + WorkspaceAppHealthDisabled: {}, + WorkspaceAppHealthInitializing: {}, + WorkspaceAppHealthHealthy: {}, + WorkspaceAppHealthUnhealthy: {}, +} + type WorkspaceAppSharingLevel string const ( @@ -21,6 +28,12 @@ const ( WorkspaceAppSharingLevelPublic WorkspaceAppSharingLevel = "public" ) +var MapWorkspaceAppSharingLevels = map[WorkspaceAppSharingLevel]struct{}{ + WorkspaceAppSharingLevelOwner: {}, + WorkspaceAppSharingLevelAuthenticated: {}, + WorkspaceAppSharingLevelPublic: {}, +} + type WorkspaceApp struct { ID uuid.UUID `json:"id" format:"uuid"` // URL is the address being proxied to inside the workspace. diff --git a/enterprise/tailnet/connio.go b/enterprise/tailnet/connio.go index 94d080e219..45d9c71c3e 100644 --- a/enterprise/tailnet/connio.go +++ b/enterprise/tailnet/connio.go @@ -139,7 +139,7 @@ func (c *connIO) handleRequest(req *proto.CoordinateRequest) error { } if req.AddTunnel != nil { c.logger.Debug(c.peerCtx, "got add tunnel", slog.F("tunnel", req.AddTunnel)) - dst, err := uuid.FromBytes(req.AddTunnel.Uuid) + dst, err := uuid.FromBytes(req.AddTunnel.Id) if err != nil { c.logger.Error(c.peerCtx, "unable to convert bytes to UUID", slog.Error(err)) // this shouldn't happen unless there is a client error. Close the connection so the client @@ -163,7 +163,7 @@ func (c *connIO) handleRequest(req *proto.CoordinateRequest) error { } if req.RemoveTunnel != nil { c.logger.Debug(c.peerCtx, "got remove tunnel", slog.F("tunnel", req.RemoveTunnel)) - dst, err := uuid.FromBytes(req.RemoveTunnel.Uuid) + dst, err := uuid.FromBytes(req.RemoveTunnel.Id) if err != nil { c.logger.Error(c.peerCtx, "unable to convert bytes to UUID", slog.Error(err)) // this shouldn't happen unless there is a client error. Close the connection so the client diff --git a/enterprise/tailnet/pgcoord.go b/enterprise/tailnet/pgcoord.go index c445634c8f..8496dfd47d 100644 --- a/enterprise/tailnet/pgcoord.go +++ b/enterprise/tailnet/pgcoord.go @@ -697,7 +697,7 @@ func (m *mapper) bestToUpdate(best map[uuid.UUID]mapping) *proto.CoordinateRespo reason = "update" } resp.PeerUpdates = append(resp.PeerUpdates, &proto.CoordinateResponse_PeerUpdate{ - Uuid: agpl.UUIDToByteSlice(k), + Id: agpl.UUIDToByteSlice(k), Node: mpng.node, Kind: mpng.kind, Reason: reason, @@ -708,7 +708,7 @@ func (m *mapper) bestToUpdate(best map[uuid.UUID]mapping) *proto.CoordinateRespo for k := range m.sent { if _, ok := best[k]; !ok { resp.PeerUpdates = append(resp.PeerUpdates, &proto.CoordinateResponse_PeerUpdate{ - Uuid: agpl.UUIDToByteSlice(k), + Id: agpl.UUIDToByteSlice(k), Kind: proto.CoordinateResponse_PeerUpdate_DISCONNECTED, Reason: "disconnected", }) diff --git a/helm/provisioner/charts/libcoder-0.1.0.tgz b/helm/provisioner/charts/libcoder-0.1.0.tgz index 4fa4539578..27cf26506a 100644 Binary files a/helm/provisioner/charts/libcoder-0.1.0.tgz and b/helm/provisioner/charts/libcoder-0.1.0.tgz differ diff --git a/tailnet/convert.go b/tailnet/convert.go index bbd4175325..a7d224dc01 100644 --- a/tailnet/convert.go +++ b/tailnet/convert.go @@ -129,10 +129,144 @@ func SingleNodeUpdate(id uuid.UUID, node *Node, reason string) (*proto.Coordinat PeerUpdates: []*proto.CoordinateResponse_PeerUpdate{ { Kind: proto.CoordinateResponse_PeerUpdate_NODE, - Uuid: UUIDToByteSlice(id), + Id: UUIDToByteSlice(id), Node: p, Reason: reason, }, }, }, nil } + +func DERPMapToProto(derpMap *tailcfg.DERPMap) *proto.DERPMap { + if derpMap == nil { + return nil + } + + regionScore := make(map[int64]float64) + if derpMap.HomeParams != nil { + for k, v := range derpMap.HomeParams.RegionScore { + regionScore[int64(k)] = v + } + } + + regions := make(map[int64]*proto.DERPMap_Region, len(derpMap.Regions)) + for regionID, region := range derpMap.Regions { + regions[int64(regionID)] = DERPRegionToProto(region) + } + + return &proto.DERPMap{ + HomeParams: &proto.DERPMap_HomeParams{ + RegionScore: regionScore, + }, + Regions: regions, + } +} + +func DERPRegionToProto(region *tailcfg.DERPRegion) *proto.DERPMap_Region { + if region == nil { + return nil + } + + regionNodes := make([]*proto.DERPMap_Region_Node, len(region.Nodes)) + for i, node := range region.Nodes { + regionNodes[i] = DERPNodeToProto(node) + } + + return &proto.DERPMap_Region{ + RegionId: int64(region.RegionID), + EmbeddedRelay: region.EmbeddedRelay, + RegionCode: region.RegionCode, + RegionName: region.RegionName, + Avoid: region.Avoid, + + Nodes: regionNodes, + } +} + +func DERPNodeToProto(node *tailcfg.DERPNode) *proto.DERPMap_Region_Node { + if node == nil { + return nil + } + + return &proto.DERPMap_Region_Node{ + Name: node.Name, + RegionId: int64(node.RegionID), + HostName: node.HostName, + CertName: node.CertName, + Ipv4: node.IPv4, + Ipv6: node.IPv6, + StunPort: int32(node.STUNPort), + StunOnly: node.STUNOnly, + DerpPort: int32(node.DERPPort), + InsecureForTests: node.InsecureForTests, + ForceHttp: node.ForceHTTP, + StunTestIp: node.STUNTestIP, + CanPort_80: node.CanPort80, + } +} + +func DERPMapFromProto(derpMap *proto.DERPMap) *tailcfg.DERPMap { + if derpMap == nil { + return nil + } + + regionScore := make(map[int]float64, len(derpMap.HomeParams.RegionScore)) + for k, v := range derpMap.HomeParams.RegionScore { + regionScore[int(k)] = v + } + + regions := make(map[int]*tailcfg.DERPRegion, len(derpMap.Regions)) + for regionID, region := range derpMap.Regions { + regions[int(regionID)] = DERPRegionFromProto(region) + } + + return &tailcfg.DERPMap{ + HomeParams: &tailcfg.DERPHomeParams{ + RegionScore: regionScore, + }, + Regions: regions, + } +} + +func DERPRegionFromProto(region *proto.DERPMap_Region) *tailcfg.DERPRegion { + if region == nil { + return nil + } + + regionNodes := make([]*tailcfg.DERPNode, len(region.Nodes)) + for i, node := range region.Nodes { + regionNodes[i] = DERPNodeFromProto(node) + } + + return &tailcfg.DERPRegion{ + RegionID: int(region.RegionId), + EmbeddedRelay: region.EmbeddedRelay, + RegionCode: region.RegionCode, + RegionName: region.RegionName, + Avoid: region.Avoid, + + Nodes: regionNodes, + } +} + +func DERPNodeFromProto(node *proto.DERPMap_Region_Node) *tailcfg.DERPNode { + if node == nil { + return nil + } + + return &tailcfg.DERPNode{ + Name: node.Name, + RegionID: int(node.RegionId), + HostName: node.HostName, + CertName: node.CertName, + IPv4: node.Ipv4, + IPv6: node.Ipv6, + STUNPort: int(node.StunPort), + STUNOnly: node.StunOnly, + DERPPort: int(node.DerpPort), + InsecureForTests: node.InsecureForTests, + ForceHTTP: node.ForceHttp, + STUNTestIP: node.StunTestIp, + CanPort80: node.CanPort_80, + } +} diff --git a/tailnet/convert_test.go b/tailnet/convert_test.go index 0998f516fa..31b5794414 100644 --- a/tailnet/convert_test.go +++ b/tailnet/convert_test.go @@ -1,7 +1,9 @@ package tailnet_test import ( + "encoding/json" "net/netip" + "os" "testing" "time" @@ -111,22 +113,22 @@ func TestOnlyNodeUpdates(t *testing.T) { resp := &proto.CoordinateResponse{ PeerUpdates: []*proto.CoordinateResponse_PeerUpdate{ { - Uuid: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, + Id: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, Kind: proto.CoordinateResponse_PeerUpdate_NODE, Node: p, }, { - Uuid: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2}, + Id: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2}, Kind: proto.CoordinateResponse_PeerUpdate_DISCONNECTED, Reason: "disconnected", }, { - Uuid: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3}, + Id: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3}, Kind: proto.CoordinateResponse_PeerUpdate_LOST, Reason: "disconnected", }, { - Uuid: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4}, + Id: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4}, }, }, } @@ -145,9 +147,63 @@ func TestSingleNodeUpdate(t *testing.T) { require.Len(t, resp.PeerUpdates, 1) up := resp.PeerUpdates[0] require.Equal(t, proto.CoordinateResponse_PeerUpdate_NODE, up.Kind) - u2, err := uuid.FromBytes(up.Uuid) + u2, err := uuid.FromBytes(up.Id) require.NoError(t, err) require.Equal(t, u, u2) require.Equal(t, "unit test", up.Reason) require.EqualValues(t, 1, up.Node.Id) } + +func TestDERPMap(t *testing.T) { + t.Parallel() + + // Tailscale DERP map on 2023-11-20 for testing purposes. + tailscaleDERPMap, err := os.ReadFile("testdata/tailscale_derpmap.json") + require.NoError(t, err) + + derpMap := &tailcfg.DERPMap{} + err = json.Unmarshal(tailscaleDERPMap, derpMap) + require.NoError(t, err) + + // The tailscale DERPMap doesn't have HomeParams. + derpMap.HomeParams = &tailcfg.DERPHomeParams{ + RegionScore: map[int]float64{ + 1: 2, + 2: 3, + }, + } + + // Add a region and node that uses every single field. + derpMap.Regions[999] = &tailcfg.DERPRegion{ + RegionID: 999, + EmbeddedRelay: true, + RegionCode: "zzz", + RegionName: "Cool Region", + Avoid: true, + + Nodes: []*tailcfg.DERPNode{ + { + Name: "zzz1", + RegionID: 999, + HostName: "coolderp.com", + IPv4: "1.2.3.4", + IPv6: "2001:db8::1", + STUNPort: 1234, + STUNOnly: true, + DERPPort: 5678, + InsecureForTests: true, + ForceHTTP: true, + STUNTestIP: "5.6.7.8", + CanPort80: true, + }, + }, + } + + protoMap := tailnet.DERPMapToProto(derpMap) + require.NotNil(t, protoMap) + + derpMap2 := tailnet.DERPMapFromProto(protoMap) + require.NotNil(t, derpMap2) + + require.Equal(t, derpMap, derpMap2) +} diff --git a/tailnet/coordinator.go b/tailnet/coordinator.go index be75a0dd11..04c5fc0ee3 100644 --- a/tailnet/coordinator.go +++ b/tailnet/coordinator.go @@ -238,11 +238,11 @@ func ServeMultiAgent(c CoordinatorV2, logger slog.Logger, id uuid.UUID) MultiAge return false }, OnSubscribe: func(enq Queue, agent uuid.UUID) (*Node, error) { - err := SendCtx(ctx, reqs, &proto.CoordinateRequest{AddTunnel: &proto.CoordinateRequest_Tunnel{Uuid: UUIDToByteSlice(agent)}}) + err := SendCtx(ctx, reqs, &proto.CoordinateRequest{AddTunnel: &proto.CoordinateRequest_Tunnel{Id: UUIDToByteSlice(agent)}}) return c.Node(agent), err }, OnUnsubscribe: func(enq Queue, agent uuid.UUID) error { - err := SendCtx(ctx, reqs, &proto.CoordinateRequest{RemoveTunnel: &proto.CoordinateRequest_Tunnel{Uuid: UUIDToByteSlice(agent)}}) + err := SendCtx(ctx, reqs, &proto.CoordinateRequest{RemoveTunnel: &proto.CoordinateRequest_Tunnel{Id: UUIDToByteSlice(agent)}}) return err }, OnNodeUpdate: func(id uuid.UUID, node *Node) error { @@ -348,7 +348,7 @@ func ServeClientV1(ctx context.Context, logger slog.Logger, c CoordinatorV2, con defer cancel() reqs, resps := c.Coordinate(ctx, id, id.String(), ClientTunnelAuth{AgentID: agent}) err := SendCtx(ctx, reqs, &proto.CoordinateRequest{ - AddTunnel: &proto.CoordinateRequest_Tunnel{Uuid: UUIDToByteSlice(agent)}, + AddTunnel: &proto.CoordinateRequest_Tunnel{Id: UUIDToByteSlice(agent)}, }) if err != nil { // can only be a context error, no need to log here. @@ -383,7 +383,7 @@ func (c *core) handleRequest(p *peer, req *proto.CoordinateRequest) error { } } if req.AddTunnel != nil { - dstID, err := uuid.FromBytes(req.AddTunnel.Uuid) + dstID, err := uuid.FromBytes(req.AddTunnel.Id) if err != nil { // this shouldn't happen unless there is a client error. Close the connection so the client // doesn't just happily continue thinking everything is fine. @@ -398,7 +398,7 @@ func (c *core) handleRequest(p *peer, req *proto.CoordinateRequest) error { } } if req.RemoveTunnel != nil { - dstID, err := uuid.FromBytes(req.RemoveTunnel.Uuid) + dstID, err := uuid.FromBytes(req.RemoveTunnel.Id) if err != nil { // this shouldn't happen unless there is a client error. Close the connection so the client // doesn't just happily continue thinking everything is fine. diff --git a/tailnet/peer.go b/tailnet/peer.go index e51aadaeac..1b9300fa30 100644 --- a/tailnet/peer.go +++ b/tailnet/peer.go @@ -114,7 +114,7 @@ func (p *peer) storeMappingLocked( p.sent[id] = n } return &proto.CoordinateResponse_PeerUpdate{ - Uuid: id[:], + Id: id[:], Kind: k, Node: n, Reason: reason, diff --git a/tailnet/proto/tailnet.pb.go b/tailnet/proto/tailnet.pb.go index c5318fa7df..f80a79660e 100644 --- a/tailnet/proto/tailnet.pb.go +++ b/tailnet/proto/tailnet.pb.go @@ -79,7 +79,7 @@ type DERPMap struct { unknownFields protoimpl.UnknownFields HomeParams *DERPMap_HomeParams `protobuf:"bytes,1,opt,name=home_params,json=homeParams,proto3" json:"home_params,omitempty"` - Regions map[int32]*DERPMap_Region `protobuf:"bytes,2,rep,name=regions,proto3" json:"regions,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + Regions map[int64]*DERPMap_Region `protobuf:"bytes,2,rep,name=regions,proto3" json:"regions,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` } func (x *DERPMap) Reset() { @@ -121,7 +121,7 @@ func (x *DERPMap) GetHomeParams() *DERPMap_HomeParams { return nil } -func (x *DERPMap) GetRegions() map[int32]*DERPMap_Region { +func (x *DERPMap) GetRegions() map[int64]*DERPMap_Region { if x != nil { return x.Regions } @@ -409,7 +409,7 @@ type DERPMap_HomeParams struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - RegionScore map[int32]float64 `protobuf:"bytes,1,rep,name=region_score,json=regionScore,proto3" json:"region_score,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"fixed64,2,opt,name=value,proto3"` + RegionScore map[int64]float64 `protobuf:"bytes,1,rep,name=region_score,json=regionScore,proto3" json:"region_score,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"fixed64,2,opt,name=value,proto3"` } func (x *DERPMap_HomeParams) Reset() { @@ -444,7 +444,7 @@ func (*DERPMap_HomeParams) Descriptor() ([]byte, []int) { return file_tailnet_proto_tailnet_proto_rawDescGZIP(), []int{0, 0} } -func (x *DERPMap_HomeParams) GetRegionScore() map[int32]float64 { +func (x *DERPMap_HomeParams) GetRegionScore() map[int64]float64 { if x != nil { return x.RegionScore } @@ -456,7 +456,7 @@ type DERPMap_Region struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - RegionId int32 `protobuf:"varint,1,opt,name=region_id,json=regionId,proto3" json:"region_id,omitempty"` + RegionId int64 `protobuf:"varint,1,opt,name=region_id,json=regionId,proto3" json:"region_id,omitempty"` EmbeddedRelay bool `protobuf:"varint,2,opt,name=embedded_relay,json=embeddedRelay,proto3" json:"embedded_relay,omitempty"` RegionCode string `protobuf:"bytes,3,opt,name=region_code,json=regionCode,proto3" json:"region_code,omitempty"` RegionName string `protobuf:"bytes,4,opt,name=region_name,json=regionName,proto3" json:"region_name,omitempty"` @@ -496,7 +496,7 @@ func (*DERPMap_Region) Descriptor() ([]byte, []int) { return file_tailnet_proto_tailnet_proto_rawDescGZIP(), []int{0, 1} } -func (x *DERPMap_Region) GetRegionId() int32 { +func (x *DERPMap_Region) GetRegionId() int64 { if x != nil { return x.RegionId } @@ -544,7 +544,7 @@ type DERPMap_Region_Node struct { unknownFields protoimpl.UnknownFields Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` - RegionId int32 `protobuf:"varint,2,opt,name=region_id,json=regionId,proto3" json:"region_id,omitempty"` + RegionId int64 `protobuf:"varint,2,opt,name=region_id,json=regionId,proto3" json:"region_id,omitempty"` HostName string `protobuf:"bytes,3,opt,name=host_name,json=hostName,proto3" json:"host_name,omitempty"` CertName string `protobuf:"bytes,4,opt,name=cert_name,json=certName,proto3" json:"cert_name,omitempty"` Ipv4 string `protobuf:"bytes,5,opt,name=ipv4,proto3" json:"ipv4,omitempty"` @@ -597,7 +597,7 @@ func (x *DERPMap_Region_Node) GetName() string { return "" } -func (x *DERPMap_Region_Node) GetRegionId() int32 { +func (x *DERPMap_Region_Node) GetRegionId() int64 { if x != nil { return x.RegionId } @@ -771,7 +771,7 @@ type CoordinateRequest_Tunnel struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Uuid []byte `protobuf:"bytes,1,opt,name=uuid,proto3" json:"uuid,omitempty"` + Id []byte `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` } func (x *CoordinateRequest_Tunnel) Reset() { @@ -806,9 +806,9 @@ func (*CoordinateRequest_Tunnel) Descriptor() ([]byte, []int) { return file_tailnet_proto_tailnet_proto_rawDescGZIP(), []int{3, 2} } -func (x *CoordinateRequest_Tunnel) GetUuid() []byte { +func (x *CoordinateRequest_Tunnel) GetId() []byte { if x != nil { - return x.Uuid + return x.Id } return nil } @@ -818,7 +818,7 @@ type CoordinateResponse_PeerUpdate struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Uuid []byte `protobuf:"bytes,1,opt,name=uuid,proto3" json:"uuid,omitempty"` + Id []byte `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` Node *Node `protobuf:"bytes,2,opt,name=node,proto3" json:"node,omitempty"` Kind CoordinateResponse_PeerUpdate_Kind `protobuf:"varint,3,opt,name=kind,proto3,enum=coder.tailnet.v2.CoordinateResponse_PeerUpdate_Kind" json:"kind,omitempty"` Reason string `protobuf:"bytes,4,opt,name=reason,proto3" json:"reason,omitempty"` @@ -856,9 +856,9 @@ func (*CoordinateResponse_PeerUpdate) Descriptor() ([]byte, []int) { return file_tailnet_proto_tailnet_proto_rawDescGZIP(), []int{4, 0} } -func (x *CoordinateResponse_PeerUpdate) GetUuid() []byte { +func (x *CoordinateResponse_PeerUpdate) GetId() []byte { if x != nil { - return x.Uuid + return x.Id } return nil } @@ -909,11 +909,11 @@ var file_tailnet_proto_tailnet_proto_rawDesc = []byte{ 0x2e, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x53, 0x63, 0x6f, 0x72, 0x65, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0b, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x53, 0x63, 0x6f, 0x72, 0x65, 0x1a, 0x3e, 0x0a, 0x10, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x53, 0x63, 0x6f, 0x72, 0x65, 0x45, 0x6e, 0x74, - 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, + 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0xe3, 0x04, 0x0a, 0x06, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x65, 0x67, - 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x72, 0x65, + 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x65, 0x6d, 0x62, 0x65, 0x64, 0x64, 0x65, 0x64, 0x5f, 0x72, 0x65, 0x6c, 0x61, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x65, 0x6d, 0x62, 0x65, 0x64, 0x64, 0x65, 0x64, 0x52, 0x65, 0x6c, 0x61, 0x79, 0x12, 0x1f, 0x0a, @@ -929,7 +929,7 @@ var file_tailnet_proto_tailnet_proto_rawDesc = []byte{ 0x65, 0x73, 0x1a, 0xff, 0x02, 0x0a, 0x04, 0x4e, 0x6f, 0x64, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x05, 0x52, 0x08, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, + 0x28, 0x03, 0x52, 0x08, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x68, 0x6f, 0x73, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x65, 0x72, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x65, @@ -952,7 +952,7 @@ var file_tailnet_proto_tailnet_proto_rawDesc = []byte{ 0x5f, 0x38, 0x30, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x63, 0x61, 0x6e, 0x50, 0x6f, 0x72, 0x74, 0x38, 0x30, 0x1a, 0x5c, 0x0a, 0x0c, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x05, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x36, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, + 0x03, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x36, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x44, 0x45, 0x52, 0x50, 0x4d, 0x61, 0x70, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, @@ -992,7 +992,7 @@ var file_tailnet_proto_tailnet_proto_rawDesc = []byte{ 0x57, 0x65, 0x62, 0x73, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xb6, 0x03, 0x0a, 0x11, 0x43, + 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xb2, 0x03, 0x0a, 0x11, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x4f, 0x0a, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x73, 0x65, 0x6c, 0x66, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2e, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, @@ -1018,46 +1018,46 @@ var file_tailnet_proto_tailnet_proto_rawDesc = []byte{ 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x04, 0x6e, 0x6f, 0x64, 0x65, 0x1a, 0x0c, 0x0a, 0x0a, 0x44, 0x69, 0x73, 0x63, 0x6f, - 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x1a, 0x1c, 0x0a, 0x06, 0x54, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x12, - 0x12, 0x0a, 0x04, 0x75, 0x75, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x75, - 0x75, 0x69, 0x64, 0x22, 0xdd, 0x02, 0x0a, 0x12, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, - 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x52, 0x0a, 0x0c, 0x70, 0x65, - 0x65, 0x72, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x2f, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, - 0x2e, 0x76, 0x32, 0x2e, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x55, 0x70, 0x64, 0x61, 0x74, - 0x65, 0x52, 0x0b, 0x70, 0x65, 0x65, 0x72, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x1a, 0xf2, - 0x01, 0x0a, 0x0a, 0x50, 0x65, 0x65, 0x72, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x12, 0x0a, - 0x04, 0x75, 0x75, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x75, 0x75, 0x69, - 0x64, 0x12, 0x2a, 0x0a, 0x04, 0x6e, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x16, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, - 0x76, 0x32, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x04, 0x6e, 0x6f, 0x64, 0x65, 0x12, 0x48, 0x0a, - 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x34, 0x2e, 0x63, 0x6f, - 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x43, - 0x6f, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x2e, 0x4b, 0x69, 0x6e, - 0x64, 0x52, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, - 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x22, - 0x42, 0x0a, 0x04, 0x4b, 0x69, 0x6e, 0x64, 0x12, 0x14, 0x0a, 0x10, 0x4b, 0x49, 0x4e, 0x44, 0x5f, - 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x08, 0x0a, - 0x04, 0x4e, 0x4f, 0x44, 0x45, 0x10, 0x01, 0x12, 0x10, 0x0a, 0x0c, 0x44, 0x49, 0x53, 0x43, 0x4f, - 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x45, 0x44, 0x10, 0x02, 0x12, 0x08, 0x0a, 0x04, 0x4c, 0x4f, 0x53, - 0x54, 0x10, 0x03, 0x32, 0xc4, 0x01, 0x0a, 0x06, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x12, 0x56, - 0x0a, 0x0e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x44, 0x45, 0x52, 0x50, 0x4d, 0x61, 0x70, 0x73, - 0x12, 0x27, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, - 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x44, 0x45, 0x52, 0x50, 0x4d, 0x61, - 0x70, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x63, 0x6f, 0x64, 0x65, - 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x44, 0x45, 0x52, - 0x50, 0x4d, 0x61, 0x70, 0x30, 0x01, 0x12, 0x62, 0x0a, 0x11, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x69, - 0x6e, 0x61, 0x74, 0x65, 0x54, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x12, 0x23, 0x2e, 0x63, 0x6f, - 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x43, - 0x6f, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x24, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, - 0x2e, 0x76, 0x32, 0x2e, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x28, 0x01, 0x30, 0x01, 0x42, 0x29, 0x5a, 0x27, 0x67, 0x69, - 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2f, 0x63, - 0x6f, 0x64, 0x65, 0x72, 0x2f, 0x76, 0x32, 0x2f, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2f, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x1a, 0x18, 0x0a, 0x06, 0x54, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x12, + 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x64, 0x22, + 0xd9, 0x02, 0x0a, 0x12, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x52, 0x0a, 0x0c, 0x70, 0x65, 0x65, 0x72, 0x5f, 0x75, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x63, + 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, + 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x0b, 0x70, + 0x65, 0x65, 0x72, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x1a, 0xee, 0x01, 0x0a, 0x0a, 0x50, + 0x65, 0x65, 0x72, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x64, 0x12, 0x2a, 0x0a, 0x04, 0x6e, 0x6f, 0x64, + 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, + 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x52, + 0x04, 0x6e, 0x6f, 0x64, 0x65, 0x12, 0x48, 0x0a, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x0e, 0x32, 0x34, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, + 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x74, + 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x55, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x2e, 0x4b, 0x69, 0x6e, 0x64, 0x52, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x12, + 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x22, 0x42, 0x0a, 0x04, 0x4b, 0x69, 0x6e, 0x64, 0x12, + 0x14, 0x0a, 0x10, 0x4b, 0x49, 0x4e, 0x44, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, + 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x4e, 0x4f, 0x44, 0x45, 0x10, 0x01, 0x12, + 0x10, 0x0a, 0x0c, 0x44, 0x49, 0x53, 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x45, 0x44, 0x10, + 0x02, 0x12, 0x08, 0x0a, 0x04, 0x4c, 0x4f, 0x53, 0x54, 0x10, 0x03, 0x32, 0xc4, 0x01, 0x0a, 0x06, + 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x12, 0x56, 0x0a, 0x0e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, + 0x44, 0x45, 0x52, 0x50, 0x4d, 0x61, 0x70, 0x73, 0x12, 0x27, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, + 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x74, 0x72, 0x65, + 0x61, 0x6d, 0x44, 0x45, 0x52, 0x50, 0x4d, 0x61, 0x70, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x19, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, + 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x44, 0x45, 0x52, 0x50, 0x4d, 0x61, 0x70, 0x30, 0x01, 0x12, 0x62, + 0x0a, 0x11, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x54, 0x61, 0x69, 0x6c, + 0x6e, 0x65, 0x74, 0x12, 0x23, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, + 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x74, + 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, + 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x43, 0x6f, 0x6f, 0x72, + 0x64, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x28, 0x01, + 0x30, 0x01, 0x42, 0x29, 0x5a, 0x27, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, + 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2f, 0x76, 0x32, 0x2f, + 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/tailnet/proto/tailnet.proto b/tailnet/proto/tailnet.proto index 8c3a1e1163..5692911a86 100644 --- a/tailnet/proto/tailnet.proto +++ b/tailnet/proto/tailnet.proto @@ -7,12 +7,12 @@ import "google/protobuf/timestamp.proto"; message DERPMap { message HomeParams { - map region_score = 1; + map region_score = 1; } HomeParams home_params = 1; message Region { - int32 region_id = 1; + int64 region_id = 1; bool embedded_relay = 2; string region_code = 3; string region_name = 4; @@ -20,7 +20,7 @@ message DERPMap { message Node { string name = 1; - int32 region_id = 2; + int64 region_id = 2; string host_name = 3; string cert_name = 4; string ipv4 = 5; @@ -35,7 +35,7 @@ message DERPMap { } repeated Node nodes = 6; } - map regions = 2; + map regions = 2; } message StreamDERPMapsRequest {} @@ -64,7 +64,7 @@ message CoordinateRequest { Disconnect disconnect = 2; message Tunnel { - bytes uuid = 1; + bytes id = 1; } Tunnel add_tunnel = 3; Tunnel remove_tunnel = 4; @@ -72,7 +72,7 @@ message CoordinateRequest { message CoordinateResponse { message PeerUpdate { - bytes uuid = 1; + bytes id = 1; Node node = 2; enum Kind { diff --git a/tailnet/service_test.go b/tailnet/service_test.go index 359d443822..c69f5b1469 100644 --- a/tailnet/service_test.go +++ b/tailnet/service_test.go @@ -133,7 +133,7 @@ func TestClientService_ServeClient_V2(t *testing.T) { { Kind: proto.CoordinateResponse_PeerUpdate_NODE, Node: &proto.Node{PreferredDerp: 22}, - Uuid: agentID[:], + Id: agentID[:], }, }} resp, err := stream.Recv() diff --git a/tailnet/test/peer.go b/tailnet/test/peer.go index dde979f813..fb01cb4d1d 100644 --- a/tailnet/test/peer.go +++ b/tailnet/test/peer.go @@ -46,7 +46,7 @@ func NewPeer(ctx context.Context, t testing.TB, coord tailnet.CoordinatorV2, nam func (p *Peer) AddTunnel(other uuid.UUID) { p.t.Helper() - req := &proto.CoordinateRequest{AddTunnel: &proto.CoordinateRequest_Tunnel{Uuid: tailnet.UUIDToByteSlice(other)}} + req := &proto.CoordinateRequest{AddTunnel: &proto.CoordinateRequest_Tunnel{Id: tailnet.UUIDToByteSlice(other)}} select { case <-p.ctx.Done(): p.t.Errorf("timeout adding tunnel for %s", p.name) @@ -146,7 +146,7 @@ func (p *Peer) handleOneResp() error { return responsesClosed } for _, update := range resp.PeerUpdates { - id, err := uuid.FromBytes(update.Uuid) + id, err := uuid.FromBytes(update.Id) if err != nil { return err } diff --git a/tailnet/testdata/tailscale_derpmap.json b/tailnet/testdata/tailscale_derpmap.json new file mode 100644 index 0000000000..956f51ac3c --- /dev/null +++ b/tailnet/testdata/tailscale_derpmap.json @@ -0,0 +1,698 @@ +{ + "Regions": { + "1": { + "RegionID": 1, + "RegionCode": "nyc", + "RegionName": "New York City", + "Nodes": [ + { + "Name": "1f", + "RegionID": 1, + "HostName": "derp1f.tailscale.com", + "IPv4": "199.38.181.104", + "IPv6": "2607:f740:f::bc" + }, + { + "Name": "1g", + "RegionID": 1, + "HostName": "derp1g.tailscale.com", + "IPv4": "209.177.145.120", + "IPv6": "2607:f740:f::3eb" + }, + { + "Name": "1h", + "RegionID": 1, + "HostName": "derp1h.tailscale.com", + "IPv4": "199.38.181.93", + "IPv6": "2607:f740:f::afd" + }, + { + "Name": "1i", + "RegionID": 1, + "HostName": "derp1i.tailscale.com", + "IPv4": "199.38.181.103", + "IPv6": "2607:f740:f::e19", + "STUNOnly": true + } + ] + }, + "10": { + "RegionID": 10, + "RegionCode": "sea", + "RegionName": "Seattle", + "Nodes": [ + { + "Name": "10b", + "RegionID": 10, + "HostName": "derp10b.tailscale.com", + "IPv4": "192.73.240.161", + "IPv6": "2607:f740:14::61c" + }, + { + "Name": "10c", + "RegionID": 10, + "HostName": "derp10c.tailscale.com", + "IPv4": "192.73.240.121", + "IPv6": "2607:f740:14::40c" + }, + { + "Name": "10d", + "RegionID": 10, + "HostName": "derp10d.tailscale.com", + "IPv4": "192.73.240.132", + "IPv6": "2607:f740:14::500" + } + ] + }, + "11": { + "RegionID": 11, + "RegionCode": "sao", + "RegionName": "São Paulo", + "Nodes": [ + { + "Name": "11b", + "RegionID": 11, + "HostName": "derp11b.tailscale.com", + "IPv4": "148.163.220.129", + "IPv6": "2607:f740:1::211" + }, + { + "Name": "11c", + "RegionID": 11, + "HostName": "derp11c.tailscale.com", + "IPv4": "148.163.220.134", + "IPv6": "2607:f740:1::861" + }, + { + "Name": "11d", + "RegionID": 11, + "HostName": "derp11d.tailscale.com", + "IPv4": "148.163.220.210", + "IPv6": "2607:f740:1::2e6" + } + ] + }, + "12": { + "RegionID": 12, + "RegionCode": "ord", + "RegionName": "Chicago", + "Nodes": [ + { + "Name": "12d", + "RegionID": 12, + "HostName": "derp12d.tailscale.com", + "IPv4": "209.177.158.246", + "IPv6": "2607:f740:e::811" + }, + { + "Name": "12e", + "RegionID": 12, + "HostName": "derp12e.tailscale.com", + "IPv4": "209.177.158.15", + "IPv6": "2607:f740:e::b17" + }, + { + "Name": "12f", + "RegionID": 12, + "HostName": "derp12f.tailscale.com", + "IPv4": "199.38.182.118", + "IPv6": "2607:f740:e::4c8" + } + ] + }, + "13": { + "RegionID": 13, + "RegionCode": "den", + "RegionName": "Denver", + "Nodes": [ + { + "Name": "13b", + "RegionID": 13, + "HostName": "derp13b.tailscale.com", + "IPv4": "192.73.242.187", + "IPv6": "2607:f740:16::640" + }, + { + "Name": "13c", + "RegionID": 13, + "HostName": "derp13c.tailscale.com", + "IPv4": "192.73.242.28", + "IPv6": "2607:f740:16::5c" + }, + { + "Name": "13d", + "RegionID": 13, + "HostName": "derp13d.tailscale.com", + "IPv4": "192.73.242.204", + "IPv6": "2607:f740:16::c23" + } + ] + }, + "14": { + "RegionID": 14, + "RegionCode": "ams", + "RegionName": "Amsterdam", + "Nodes": [ + { + "Name": "14b", + "RegionID": 14, + "HostName": "derp14b.tailscale.com", + "IPv4": "176.58.93.248", + "IPv6": "2a00:dd80:3c::807" + }, + { + "Name": "14c", + "RegionID": 14, + "HostName": "derp14c.tailscale.com", + "IPv4": "176.58.93.147", + "IPv6": "2a00:dd80:3c::b09" + }, + { + "Name": "14d", + "RegionID": 14, + "HostName": "derp14d.tailscale.com", + "IPv4": "176.58.93.154", + "IPv6": "2a00:dd80:3c::3d5" + } + ] + }, + "15": { + "RegionID": 15, + "RegionCode": "jnb", + "RegionName": "Johannesburg", + "Nodes": [ + { + "Name": "15b", + "RegionID": 15, + "HostName": "derp15b.tailscale.com", + "IPv4": "102.67.165.90", + "IPv6": "2c0f:edb0:0:10::963" + }, + { + "Name": "15c", + "RegionID": 15, + "HostName": "derp15c.tailscale.com", + "IPv4": "102.67.165.185", + "IPv6": "2c0f:edb0:0:10::b59" + }, + { + "Name": "15d", + "RegionID": 15, + "HostName": "derp15d.tailscale.com", + "IPv4": "102.67.165.36", + "IPv6": "2c0f:edb0:0:10::599" + } + ] + }, + "16": { + "RegionID": 16, + "RegionCode": "mia", + "RegionName": "Miami", + "Nodes": [ + { + "Name": "16b", + "RegionID": 16, + "HostName": "derp16b.tailscale.com", + "IPv4": "192.73.243.135", + "IPv6": "2607:f740:17::476" + }, + { + "Name": "16c", + "RegionID": 16, + "HostName": "derp16c.tailscale.com", + "IPv4": "192.73.243.229", + "IPv6": "2607:f740:17::4e4" + }, + { + "Name": "16d", + "RegionID": 16, + "HostName": "derp16d.tailscale.com", + "IPv4": "192.73.243.141", + "IPv6": "2607:f740:17::475" + } + ] + }, + "17": { + "RegionID": 17, + "RegionCode": "lax", + "RegionName": "Los Angeles", + "Nodes": [ + { + "Name": "17b", + "RegionID": 17, + "HostName": "derp17b.tailscale.com", + "IPv4": "192.73.244.245", + "IPv6": "2607:f740:c::646" + }, + { + "Name": "17c", + "RegionID": 17, + "HostName": "derp17c.tailscale.com", + "IPv4": "208.111.40.12", + "IPv6": "2607:f740:c::10" + }, + { + "Name": "17d", + "RegionID": 17, + "HostName": "derp17d.tailscale.com", + "IPv4": "208.111.40.216", + "IPv6": "2607:f740:c::e1b" + } + ] + }, + "18": { + "RegionID": 18, + "RegionCode": "par", + "RegionName": "Paris", + "Nodes": [ + { + "Name": "18b", + "RegionID": 18, + "HostName": "derp18b.tailscale.com", + "IPv4": "176.58.90.147", + "IPv6": "2a00:dd80:3e::363" + }, + { + "Name": "18c", + "RegionID": 18, + "HostName": "derp18c.tailscale.com", + "IPv4": "176.58.90.207", + "IPv6": "2a00:dd80:3e::c19" + }, + { + "Name": "18d", + "RegionID": 18, + "HostName": "derp18d.tailscale.com", + "IPv4": "176.58.90.104", + "IPv6": "2a00:dd80:3e::f2e" + } + ] + }, + "19": { + "RegionID": 19, + "RegionCode": "mad", + "RegionName": "Madrid", + "Nodes": [ + { + "Name": "19b", + "RegionID": 19, + "HostName": "derp19b.tailscale.com", + "IPv4": "45.159.97.144", + "IPv6": "2a00:dd80:14:10::335" + }, + { + "Name": "19c", + "RegionID": 19, + "HostName": "derp19c.tailscale.com", + "IPv4": "45.159.97.61", + "IPv6": "2a00:dd80:14:10::20" + }, + { + "Name": "19d", + "RegionID": 19, + "HostName": "derp19d.tailscale.com", + "IPv4": "45.159.97.233", + "IPv6": "2a00:dd80:14:10::34a" + } + ] + }, + "2": { + "RegionID": 2, + "RegionCode": "sfo", + "RegionName": "San Francisco", + "Nodes": [ + { + "Name": "2d", + "RegionID": 2, + "HostName": "derp2d.tailscale.com", + "IPv4": "192.73.252.65", + "IPv6": "2607:f740:0:3f::287" + }, + { + "Name": "2e", + "RegionID": 2, + "HostName": "derp2e.tailscale.com", + "IPv4": "192.73.252.134", + "IPv6": "2607:f740:0:3f::44c" + }, + { + "Name": "2f", + "RegionID": 2, + "HostName": "derp2f.tailscale.com", + "IPv4": "208.111.34.178", + "IPv6": "2607:f740:0:3f::f4" + } + ] + }, + "20": { + "RegionID": 20, + "RegionCode": "hkg", + "RegionName": "Hong Kong", + "Nodes": [ + { + "Name": "20b", + "RegionID": 20, + "HostName": "derp20b.tailscale.com", + "IPv4": "103.6.84.152", + "IPv6": "2403:2500:8000:1::ef6" + }, + { + "Name": "20c", + "RegionID": 20, + "HostName": "derp20c.tailscale.com", + "IPv4": "205.147.105.30", + "IPv6": "2403:2500:8000:1::5fb" + }, + { + "Name": "20d", + "RegionID": 20, + "HostName": "derp20d.tailscale.com", + "IPv4": "205.147.105.78", + "IPv6": "2403:2500:8000:1::e9a" + } + ] + }, + "21": { + "RegionID": 21, + "RegionCode": "tor", + "RegionName": "Toronto", + "Nodes": [ + { + "Name": "21b", + "RegionID": 21, + "HostName": "derp21b.tailscale.com", + "IPv4": "162.248.221.199", + "IPv6": "2607:f740:50::1d1" + }, + { + "Name": "21c", + "RegionID": 21, + "HostName": "derp21c.tailscale.com", + "IPv4": "162.248.221.215", + "IPv6": "2607:f740:50::f10" + }, + { + "Name": "21d", + "RegionID": 21, + "HostName": "derp21d.tailscale.com", + "IPv4": "162.248.221.248", + "IPv6": "2607:f740:50::ca4" + } + ] + }, + "22": { + "RegionID": 22, + "RegionCode": "waw", + "RegionName": "Warsaw", + "Nodes": [ + { + "Name": "22b", + "RegionID": 22, + "HostName": "derp22b.tailscale.com", + "IPv4": "45.159.98.196", + "IPv6": "2a00:dd80:40:100::316" + }, + { + "Name": "22c", + "RegionID": 22, + "HostName": "derp22c.tailscale.com", + "IPv4": "45.159.98.253", + "IPv6": "2a00:dd80:40:100::3f" + }, + { + "Name": "22d", + "RegionID": 22, + "HostName": "derp22d.tailscale.com", + "IPv4": "45.159.98.145", + "IPv6": "2a00:dd80:40:100::211" + } + ] + }, + "23": { + "RegionID": 23, + "RegionCode": "dbi", + "RegionName": "Dubai", + "Nodes": [ + { + "Name": "23b", + "RegionID": 23, + "HostName": "derp23b.tailscale.com", + "IPv4": "185.34.3.232", + "IPv6": "2a00:dd80:3f:100::76f" + }, + { + "Name": "23c", + "RegionID": 23, + "HostName": "derp23c.tailscale.com", + "IPv4": "185.34.3.207", + "IPv6": "2a00:dd80:3f:100::a50" + }, + { + "Name": "23d", + "RegionID": 23, + "HostName": "derp23d.tailscale.com", + "IPv4": "185.34.3.75", + "IPv6": "2a00:dd80:3f:100::97e" + } + ] + }, + "24": { + "RegionID": 24, + "RegionCode": "hnl", + "RegionName": "Honolulu", + "Nodes": [ + { + "Name": "24b", + "RegionID": 24, + "HostName": "derp24b.tailscale.com", + "IPv4": "208.83.234.151", + "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6" + }, + { + "Name": "24c", + "RegionID": 24, + "HostName": "derp24c.tailscale.com", + "IPv4": "208.83.233.233", + "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f" + }, + { + "Name": "24d", + "RegionID": 24, + "HostName": "derp24d.tailscale.com", + "IPv4": "208.72.155.133", + "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8" + } + ] + }, + "25": { + "RegionID": 25, + "RegionCode": "nai", + "RegionName": "Nairobi", + "Nodes": [ + { + "Name": "25b", + "RegionID": 25, + "HostName": "derp25b.tailscale.com", + "IPv4": "102.67.167.245", + "IPv6": "2c0f:edb0:2000:1::2e9" + }, + { + "Name": "25c", + "RegionID": 25, + "HostName": "derp25c.tailscale.com", + "IPv4": "102.67.167.37", + "IPv6": "2c0f:edb0:2000:1::2c7" + }, + { + "Name": "25d", + "RegionID": 25, + "HostName": "derp25d.tailscale.com", + "IPv4": "102.67.167.188", + "IPv6": "2c0f:edb0:2000:1::188" + } + ] + }, + "3": { + "RegionID": 3, + "RegionCode": "sin", + "RegionName": "Singapore", + "Nodes": [ + { + "Name": "3b", + "RegionID": 3, + "HostName": "derp3b.tailscale.com", + "IPv4": "43.245.49.105", + "IPv6": "2403:2500:300::b0c" + }, + { + "Name": "3c", + "RegionID": 3, + "HostName": "derp3c.tailscale.com", + "IPv4": "43.245.49.83", + "IPv6": "2403:2500:300::57a" + }, + { + "Name": "3d", + "RegionID": 3, + "HostName": "derp3d.tailscale.com", + "IPv4": "43.245.49.144", + "IPv6": "2403:2500:300::df9" + } + ] + }, + "4": { + "RegionID": 4, + "RegionCode": "fra", + "RegionName": "Frankfurt", + "Nodes": [ + { + "Name": "4f", + "RegionID": 4, + "HostName": "derp4f.tailscale.com", + "IPv4": "185.40.234.219", + "IPv6": "2a00:dd80:20::a25" + }, + { + "Name": "4g", + "RegionID": 4, + "HostName": "derp4g.tailscale.com", + "IPv4": "185.40.234.113", + "IPv6": "2a00:dd80:20::8f" + }, + { + "Name": "4h", + "RegionID": 4, + "HostName": "derp4h.tailscale.com", + "IPv4": "185.40.234.77", + "IPv6": "2a00:dd80:20::bcf" + } + ] + }, + "5": { + "RegionID": 5, + "RegionCode": "syd", + "RegionName": "Sydney", + "Nodes": [ + { + "Name": "5b", + "RegionID": 5, + "HostName": "derp5b.tailscale.com", + "IPv4": "43.245.48.220", + "IPv6": "2403:2500:9000:1::ce7" + }, + { + "Name": "5c", + "RegionID": 5, + "HostName": "derp5c.tailscale.com", + "IPv4": "43.245.48.50", + "IPv6": "2403:2500:9000:1::f57" + }, + { + "Name": "5d", + "RegionID": 5, + "HostName": "derp5d.tailscale.com", + "IPv4": "43.245.48.250", + "IPv6": "2403:2500:9000:1::43" + } + ] + }, + "6": { + "RegionID": 6, + "RegionCode": "blr", + "RegionName": "Bangalore", + "Nodes": [ + { + "Name": "6a", + "RegionID": 6, + "HostName": "derp6.tailscale.com", + "IPv4": "68.183.90.120", + "IPv6": "2400:6180:100:d0::982:d001" + } + ] + }, + "7": { + "RegionID": 7, + "RegionCode": "tok", + "RegionName": "Tokyo", + "Nodes": [ + { + "Name": "7b", + "RegionID": 7, + "HostName": "derp7b.tailscale.com", + "IPv4": "103.84.155.178", + "IPv6": "2403:2500:400:20::b79" + }, + { + "Name": "7c", + "RegionID": 7, + "HostName": "derp7c.tailscale.com", + "IPv4": "103.84.155.188", + "IPv6": "2403:2500:400:20::835" + }, + { + "Name": "7d", + "RegionID": 7, + "HostName": "derp7d.tailscale.com", + "IPv4": "103.84.155.46", + "IPv6": "2403:2500:400:20::cfe" + } + ] + }, + "8": { + "RegionID": 8, + "RegionCode": "lhr", + "RegionName": "London", + "Nodes": [ + { + "Name": "8e", + "RegionID": 8, + "HostName": "derp8e.tailscale.com", + "IPv4": "176.58.92.144", + "IPv6": "2a00:dd80:3a::b33" + }, + { + "Name": "8f", + "RegionID": 8, + "HostName": "derp8f.tailscale.com", + "IPv4": "176.58.88.183", + "IPv6": "2a00:dd80:3a::dfa" + }, + { + "Name": "8g", + "RegionID": 8, + "HostName": "derp8g.tailscale.com", + "IPv4": "176.58.92.254", + "IPv6": "2a00:dd80:3a::ed" + } + ] + }, + "9": { + "RegionID": 9, + "RegionCode": "dfw", + "RegionName": "Dallas", + "Nodes": [ + { + "Name": "9d", + "RegionID": 9, + "HostName": "derp9d.tailscale.com", + "IPv4": "209.177.156.94", + "IPv6": "2607:f740:100::c05" + }, + { + "Name": "9e", + "RegionID": 9, + "HostName": "derp9e.tailscale.com", + "IPv4": "192.73.248.83", + "IPv6": "2607:f740:100::359" + }, + { + "Name": "9f", + "RegionID": 9, + "HostName": "derp9f.tailscale.com", + "IPv4": "209.177.156.197", + "IPv6": "2607:f740:100::cad" + } + ] + } + } +}