From 777ba8a0e14d3bb6c80cfca0d08b267b5887ba0b Mon Sep 17 00:00:00 2001 From: Kyle Isom Date: Fri, 27 Mar 2026 01:04:47 -0700 Subject: [PATCH] Add route declarations and automatic port allocation to MCP agent MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Service definitions can now declare routes per component instead of manual port mappings: [[components.routes]] name = "rest" port = 8443 mode = "l4" The agent allocates free host ports at deploy time and injects $PORT/$PORT_ env vars into containers. Backward compatible: components with old-style ports= work unchanged. Changes: - Proto: RouteSpec message, routes + env fields on ComponentSpec - Servicedef: RouteDef parsing and validation from TOML - Registry: component_routes table with host_port tracking - Runtime: Env field on ContainerSpec, -e flag in BuildRunArgs - Agent: PortAllocator (random 10000-60000, availability check), deploy wiring for route→port mapping and env injection Co-Authored-By: Claude Opus 4.6 (1M context) --- gen/mcp/v1/mcp.pb.go | 492 +++++++++++++++---------- internal/agent/agent.go | 22 +- internal/agent/deploy.go | 71 +++- internal/agent/portalloc.go | 69 ++++ internal/agent/portalloc_test.go | 65 ++++ internal/registry/components.go | 103 ++++++ internal/registry/db.go | 15 + internal/registry/registry_test.go | 154 ++++++++ internal/runtime/podman.go | 3 + internal/runtime/runtime.go | 1 + internal/runtime/runtime_test.go | 32 ++ internal/servicedef/servicedef.go | 90 ++++- internal/servicedef/servicedef_test.go | 197 ++++++++++ proto/mcp/v1/mcp.proto | 9 + 14 files changed, 1101 insertions(+), 222 deletions(-) create mode 100644 internal/agent/portalloc.go create mode 100644 internal/agent/portalloc_test.go diff --git a/gen/mcp/v1/mcp.pb.go b/gen/mcp/v1/mcp.pb.go index 4b16694..8e5c14b 100644 --- a/gen/mcp/v1/mcp.pb.go +++ b/gen/mcp/v1/mcp.pb.go @@ -22,6 +22,74 @@ const ( _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) +type RouteSpec struct { + state protoimpl.MessageState `protogen:"open.v1"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` // route name (used for $PORT_) + Port int32 `protobuf:"varint,2,opt,name=port,proto3" json:"port,omitempty"` // external port on mc-proxy + Mode string `protobuf:"bytes,3,opt,name=mode,proto3" json:"mode,omitempty"` // "l4" or "l7" + Hostname string `protobuf:"bytes,4,opt,name=hostname,proto3" json:"hostname,omitempty"` // optional public hostname override + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RouteSpec) Reset() { + *x = RouteSpec{} + mi := &file_proto_mcp_v1_mcp_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RouteSpec) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RouteSpec) ProtoMessage() {} + +func (x *RouteSpec) ProtoReflect() protoreflect.Message { + mi := &file_proto_mcp_v1_mcp_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RouteSpec.ProtoReflect.Descriptor instead. +func (*RouteSpec) Descriptor() ([]byte, []int) { + return file_proto_mcp_v1_mcp_proto_rawDescGZIP(), []int{0} +} + +func (x *RouteSpec) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *RouteSpec) GetPort() int32 { + if x != nil { + return x.Port + } + return 0 +} + +func (x *RouteSpec) GetMode() string { + if x != nil { + return x.Mode + } + return "" +} + +func (x *RouteSpec) GetHostname() string { + if x != nil { + return x.Hostname + } + return "" +} + type ComponentSpec struct { state protoimpl.MessageState `protogen:"open.v1"` Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` @@ -32,13 +100,15 @@ type ComponentSpec struct { Ports []string `protobuf:"bytes,6,rep,name=ports,proto3" json:"ports,omitempty"` Volumes []string `protobuf:"bytes,7,rep,name=volumes,proto3" json:"volumes,omitempty"` Cmd []string `protobuf:"bytes,8,rep,name=cmd,proto3" json:"cmd,omitempty"` + Routes []*RouteSpec `protobuf:"bytes,9,rep,name=routes,proto3" json:"routes,omitempty"` + Env []string `protobuf:"bytes,10,rep,name=env,proto3" json:"env,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *ComponentSpec) Reset() { *x = ComponentSpec{} - mi := &file_proto_mcp_v1_mcp_proto_msgTypes[0] + mi := &file_proto_mcp_v1_mcp_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -50,7 +120,7 @@ func (x *ComponentSpec) String() string { func (*ComponentSpec) ProtoMessage() {} func (x *ComponentSpec) ProtoReflect() protoreflect.Message { - mi := &file_proto_mcp_v1_mcp_proto_msgTypes[0] + mi := &file_proto_mcp_v1_mcp_proto_msgTypes[1] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -63,7 +133,7 @@ func (x *ComponentSpec) ProtoReflect() protoreflect.Message { // Deprecated: Use ComponentSpec.ProtoReflect.Descriptor instead. func (*ComponentSpec) Descriptor() ([]byte, []int) { - return file_proto_mcp_v1_mcp_proto_rawDescGZIP(), []int{0} + return file_proto_mcp_v1_mcp_proto_rawDescGZIP(), []int{1} } func (x *ComponentSpec) GetName() string { @@ -122,6 +192,20 @@ func (x *ComponentSpec) GetCmd() []string { return nil } +func (x *ComponentSpec) GetRoutes() []*RouteSpec { + if x != nil { + return x.Routes + } + return nil +} + +func (x *ComponentSpec) GetEnv() []string { + if x != nil { + return x.Env + } + return nil +} + type ServiceSpec struct { state protoimpl.MessageState `protogen:"open.v1"` Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` @@ -133,7 +217,7 @@ type ServiceSpec struct { func (x *ServiceSpec) Reset() { *x = ServiceSpec{} - mi := &file_proto_mcp_v1_mcp_proto_msgTypes[1] + mi := &file_proto_mcp_v1_mcp_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -145,7 +229,7 @@ func (x *ServiceSpec) String() string { func (*ServiceSpec) ProtoMessage() {} func (x *ServiceSpec) ProtoReflect() protoreflect.Message { - mi := &file_proto_mcp_v1_mcp_proto_msgTypes[1] + mi := &file_proto_mcp_v1_mcp_proto_msgTypes[2] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -158,7 +242,7 @@ func (x *ServiceSpec) ProtoReflect() protoreflect.Message { // Deprecated: Use ServiceSpec.ProtoReflect.Descriptor instead. func (*ServiceSpec) Descriptor() ([]byte, []int) { - return file_proto_mcp_v1_mcp_proto_rawDescGZIP(), []int{1} + return file_proto_mcp_v1_mcp_proto_rawDescGZIP(), []int{2} } func (x *ServiceSpec) GetName() string { @@ -193,7 +277,7 @@ type DeployRequest struct { func (x *DeployRequest) Reset() { *x = DeployRequest{} - mi := &file_proto_mcp_v1_mcp_proto_msgTypes[2] + mi := &file_proto_mcp_v1_mcp_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -205,7 +289,7 @@ func (x *DeployRequest) String() string { func (*DeployRequest) ProtoMessage() {} func (x *DeployRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_mcp_v1_mcp_proto_msgTypes[2] + mi := &file_proto_mcp_v1_mcp_proto_msgTypes[3] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -218,7 +302,7 @@ func (x *DeployRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DeployRequest.ProtoReflect.Descriptor instead. func (*DeployRequest) Descriptor() ([]byte, []int) { - return file_proto_mcp_v1_mcp_proto_rawDescGZIP(), []int{2} + return file_proto_mcp_v1_mcp_proto_rawDescGZIP(), []int{3} } func (x *DeployRequest) GetService() *ServiceSpec { @@ -244,7 +328,7 @@ type DeployResponse struct { func (x *DeployResponse) Reset() { *x = DeployResponse{} - mi := &file_proto_mcp_v1_mcp_proto_msgTypes[3] + mi := &file_proto_mcp_v1_mcp_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -256,7 +340,7 @@ func (x *DeployResponse) String() string { func (*DeployResponse) ProtoMessage() {} func (x *DeployResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_mcp_v1_mcp_proto_msgTypes[3] + mi := &file_proto_mcp_v1_mcp_proto_msgTypes[4] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -269,7 +353,7 @@ func (x *DeployResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DeployResponse.ProtoReflect.Descriptor instead. func (*DeployResponse) Descriptor() ([]byte, []int) { - return file_proto_mcp_v1_mcp_proto_rawDescGZIP(), []int{3} + return file_proto_mcp_v1_mcp_proto_rawDescGZIP(), []int{4} } func (x *DeployResponse) GetResults() []*ComponentResult { @@ -290,7 +374,7 @@ type ComponentResult struct { func (x *ComponentResult) Reset() { *x = ComponentResult{} - mi := &file_proto_mcp_v1_mcp_proto_msgTypes[4] + mi := &file_proto_mcp_v1_mcp_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -302,7 +386,7 @@ func (x *ComponentResult) String() string { func (*ComponentResult) ProtoMessage() {} func (x *ComponentResult) ProtoReflect() protoreflect.Message { - mi := &file_proto_mcp_v1_mcp_proto_msgTypes[4] + mi := &file_proto_mcp_v1_mcp_proto_msgTypes[5] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -315,7 +399,7 @@ func (x *ComponentResult) ProtoReflect() protoreflect.Message { // Deprecated: Use ComponentResult.ProtoReflect.Descriptor instead. func (*ComponentResult) Descriptor() ([]byte, []int) { - return file_proto_mcp_v1_mcp_proto_rawDescGZIP(), []int{4} + return file_proto_mcp_v1_mcp_proto_rawDescGZIP(), []int{5} } func (x *ComponentResult) GetName() string { @@ -348,7 +432,7 @@ type StopServiceRequest struct { func (x *StopServiceRequest) Reset() { *x = StopServiceRequest{} - mi := &file_proto_mcp_v1_mcp_proto_msgTypes[5] + mi := &file_proto_mcp_v1_mcp_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -360,7 +444,7 @@ func (x *StopServiceRequest) String() string { func (*StopServiceRequest) ProtoMessage() {} func (x *StopServiceRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_mcp_v1_mcp_proto_msgTypes[5] + mi := &file_proto_mcp_v1_mcp_proto_msgTypes[6] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -373,7 +457,7 @@ func (x *StopServiceRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use StopServiceRequest.ProtoReflect.Descriptor instead. func (*StopServiceRequest) Descriptor() ([]byte, []int) { - return file_proto_mcp_v1_mcp_proto_rawDescGZIP(), []int{5} + return file_proto_mcp_v1_mcp_proto_rawDescGZIP(), []int{6} } func (x *StopServiceRequest) GetName() string { @@ -392,7 +476,7 @@ type StopServiceResponse struct { func (x *StopServiceResponse) Reset() { *x = StopServiceResponse{} - mi := &file_proto_mcp_v1_mcp_proto_msgTypes[6] + mi := &file_proto_mcp_v1_mcp_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -404,7 +488,7 @@ func (x *StopServiceResponse) String() string { func (*StopServiceResponse) ProtoMessage() {} func (x *StopServiceResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_mcp_v1_mcp_proto_msgTypes[6] + mi := &file_proto_mcp_v1_mcp_proto_msgTypes[7] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -417,7 +501,7 @@ func (x *StopServiceResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use StopServiceResponse.ProtoReflect.Descriptor instead. func (*StopServiceResponse) Descriptor() ([]byte, []int) { - return file_proto_mcp_v1_mcp_proto_rawDescGZIP(), []int{6} + return file_proto_mcp_v1_mcp_proto_rawDescGZIP(), []int{7} } func (x *StopServiceResponse) GetResults() []*ComponentResult { @@ -436,7 +520,7 @@ type StartServiceRequest struct { func (x *StartServiceRequest) Reset() { *x = StartServiceRequest{} - mi := &file_proto_mcp_v1_mcp_proto_msgTypes[7] + mi := &file_proto_mcp_v1_mcp_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -448,7 +532,7 @@ func (x *StartServiceRequest) String() string { func (*StartServiceRequest) ProtoMessage() {} func (x *StartServiceRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_mcp_v1_mcp_proto_msgTypes[7] + mi := &file_proto_mcp_v1_mcp_proto_msgTypes[8] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -461,7 +545,7 @@ func (x *StartServiceRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use StartServiceRequest.ProtoReflect.Descriptor instead. func (*StartServiceRequest) Descriptor() ([]byte, []int) { - return file_proto_mcp_v1_mcp_proto_rawDescGZIP(), []int{7} + return file_proto_mcp_v1_mcp_proto_rawDescGZIP(), []int{8} } func (x *StartServiceRequest) GetName() string { @@ -480,7 +564,7 @@ type StartServiceResponse struct { func (x *StartServiceResponse) Reset() { *x = StartServiceResponse{} - mi := &file_proto_mcp_v1_mcp_proto_msgTypes[8] + mi := &file_proto_mcp_v1_mcp_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -492,7 +576,7 @@ func (x *StartServiceResponse) String() string { func (*StartServiceResponse) ProtoMessage() {} func (x *StartServiceResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_mcp_v1_mcp_proto_msgTypes[8] + mi := &file_proto_mcp_v1_mcp_proto_msgTypes[9] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -505,7 +589,7 @@ func (x *StartServiceResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use StartServiceResponse.ProtoReflect.Descriptor instead. func (*StartServiceResponse) Descriptor() ([]byte, []int) { - return file_proto_mcp_v1_mcp_proto_rawDescGZIP(), []int{8} + return file_proto_mcp_v1_mcp_proto_rawDescGZIP(), []int{9} } func (x *StartServiceResponse) GetResults() []*ComponentResult { @@ -524,7 +608,7 @@ type RestartServiceRequest struct { func (x *RestartServiceRequest) Reset() { *x = RestartServiceRequest{} - mi := &file_proto_mcp_v1_mcp_proto_msgTypes[9] + mi := &file_proto_mcp_v1_mcp_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -536,7 +620,7 @@ func (x *RestartServiceRequest) String() string { func (*RestartServiceRequest) ProtoMessage() {} func (x *RestartServiceRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_mcp_v1_mcp_proto_msgTypes[9] + mi := &file_proto_mcp_v1_mcp_proto_msgTypes[10] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -549,7 +633,7 @@ func (x *RestartServiceRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RestartServiceRequest.ProtoReflect.Descriptor instead. func (*RestartServiceRequest) Descriptor() ([]byte, []int) { - return file_proto_mcp_v1_mcp_proto_rawDescGZIP(), []int{9} + return file_proto_mcp_v1_mcp_proto_rawDescGZIP(), []int{10} } func (x *RestartServiceRequest) GetName() string { @@ -568,7 +652,7 @@ type RestartServiceResponse struct { func (x *RestartServiceResponse) Reset() { *x = RestartServiceResponse{} - mi := &file_proto_mcp_v1_mcp_proto_msgTypes[10] + mi := &file_proto_mcp_v1_mcp_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -580,7 +664,7 @@ func (x *RestartServiceResponse) String() string { func (*RestartServiceResponse) ProtoMessage() {} func (x *RestartServiceResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_mcp_v1_mcp_proto_msgTypes[10] + mi := &file_proto_mcp_v1_mcp_proto_msgTypes[11] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -593,7 +677,7 @@ func (x *RestartServiceResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RestartServiceResponse.ProtoReflect.Descriptor instead. func (*RestartServiceResponse) Descriptor() ([]byte, []int) { - return file_proto_mcp_v1_mcp_proto_rawDescGZIP(), []int{10} + return file_proto_mcp_v1_mcp_proto_rawDescGZIP(), []int{11} } func (x *RestartServiceResponse) GetResults() []*ComponentResult { @@ -613,7 +697,7 @@ type SyncDesiredStateRequest struct { func (x *SyncDesiredStateRequest) Reset() { *x = SyncDesiredStateRequest{} - mi := &file_proto_mcp_v1_mcp_proto_msgTypes[11] + mi := &file_proto_mcp_v1_mcp_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -625,7 +709,7 @@ func (x *SyncDesiredStateRequest) String() string { func (*SyncDesiredStateRequest) ProtoMessage() {} func (x *SyncDesiredStateRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_mcp_v1_mcp_proto_msgTypes[11] + mi := &file_proto_mcp_v1_mcp_proto_msgTypes[12] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -638,7 +722,7 @@ func (x *SyncDesiredStateRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SyncDesiredStateRequest.ProtoReflect.Descriptor instead. func (*SyncDesiredStateRequest) Descriptor() ([]byte, []int) { - return file_proto_mcp_v1_mcp_proto_rawDescGZIP(), []int{11} + return file_proto_mcp_v1_mcp_proto_rawDescGZIP(), []int{12} } func (x *SyncDesiredStateRequest) GetServices() []*ServiceSpec { @@ -657,7 +741,7 @@ type SyncDesiredStateResponse struct { func (x *SyncDesiredStateResponse) Reset() { *x = SyncDesiredStateResponse{} - mi := &file_proto_mcp_v1_mcp_proto_msgTypes[12] + mi := &file_proto_mcp_v1_mcp_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -669,7 +753,7 @@ func (x *SyncDesiredStateResponse) String() string { func (*SyncDesiredStateResponse) ProtoMessage() {} func (x *SyncDesiredStateResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_mcp_v1_mcp_proto_msgTypes[12] + mi := &file_proto_mcp_v1_mcp_proto_msgTypes[13] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -682,7 +766,7 @@ func (x *SyncDesiredStateResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SyncDesiredStateResponse.ProtoReflect.Descriptor instead. func (*SyncDesiredStateResponse) Descriptor() ([]byte, []int) { - return file_proto_mcp_v1_mcp_proto_rawDescGZIP(), []int{12} + return file_proto_mcp_v1_mcp_proto_rawDescGZIP(), []int{13} } func (x *SyncDesiredStateResponse) GetResults() []*ServiceSyncResult { @@ -704,7 +788,7 @@ type ServiceSyncResult struct { func (x *ServiceSyncResult) Reset() { *x = ServiceSyncResult{} - mi := &file_proto_mcp_v1_mcp_proto_msgTypes[13] + mi := &file_proto_mcp_v1_mcp_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -716,7 +800,7 @@ func (x *ServiceSyncResult) String() string { func (*ServiceSyncResult) ProtoMessage() {} func (x *ServiceSyncResult) ProtoReflect() protoreflect.Message { - mi := &file_proto_mcp_v1_mcp_proto_msgTypes[13] + mi := &file_proto_mcp_v1_mcp_proto_msgTypes[14] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -729,7 +813,7 @@ func (x *ServiceSyncResult) ProtoReflect() protoreflect.Message { // Deprecated: Use ServiceSyncResult.ProtoReflect.Descriptor instead. func (*ServiceSyncResult) Descriptor() ([]byte, []int) { - return file_proto_mcp_v1_mcp_proto_rawDescGZIP(), []int{13} + return file_proto_mcp_v1_mcp_proto_rawDescGZIP(), []int{14} } func (x *ServiceSyncResult) GetName() string { @@ -761,7 +845,7 @@ type ListServicesRequest struct { func (x *ListServicesRequest) Reset() { *x = ListServicesRequest{} - mi := &file_proto_mcp_v1_mcp_proto_msgTypes[14] + mi := &file_proto_mcp_v1_mcp_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -773,7 +857,7 @@ func (x *ListServicesRequest) String() string { func (*ListServicesRequest) ProtoMessage() {} func (x *ListServicesRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_mcp_v1_mcp_proto_msgTypes[14] + mi := &file_proto_mcp_v1_mcp_proto_msgTypes[15] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -786,7 +870,7 @@ func (x *ListServicesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListServicesRequest.ProtoReflect.Descriptor instead. func (*ListServicesRequest) Descriptor() ([]byte, []int) { - return file_proto_mcp_v1_mcp_proto_rawDescGZIP(), []int{14} + return file_proto_mcp_v1_mcp_proto_rawDescGZIP(), []int{15} } type ServiceInfo struct { @@ -800,7 +884,7 @@ type ServiceInfo struct { func (x *ServiceInfo) Reset() { *x = ServiceInfo{} - mi := &file_proto_mcp_v1_mcp_proto_msgTypes[15] + mi := &file_proto_mcp_v1_mcp_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -812,7 +896,7 @@ func (x *ServiceInfo) String() string { func (*ServiceInfo) ProtoMessage() {} func (x *ServiceInfo) ProtoReflect() protoreflect.Message { - mi := &file_proto_mcp_v1_mcp_proto_msgTypes[15] + mi := &file_proto_mcp_v1_mcp_proto_msgTypes[16] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -825,7 +909,7 @@ func (x *ServiceInfo) ProtoReflect() protoreflect.Message { // Deprecated: Use ServiceInfo.ProtoReflect.Descriptor instead. func (*ServiceInfo) Descriptor() ([]byte, []int) { - return file_proto_mcp_v1_mcp_proto_rawDescGZIP(), []int{15} + return file_proto_mcp_v1_mcp_proto_rawDescGZIP(), []int{16} } func (x *ServiceInfo) GetName() string { @@ -866,7 +950,7 @@ type ComponentInfo struct { func (x *ComponentInfo) Reset() { *x = ComponentInfo{} - mi := &file_proto_mcp_v1_mcp_proto_msgTypes[16] + mi := &file_proto_mcp_v1_mcp_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -878,7 +962,7 @@ func (x *ComponentInfo) String() string { func (*ComponentInfo) ProtoMessage() {} func (x *ComponentInfo) ProtoReflect() protoreflect.Message { - mi := &file_proto_mcp_v1_mcp_proto_msgTypes[16] + mi := &file_proto_mcp_v1_mcp_proto_msgTypes[17] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -891,7 +975,7 @@ func (x *ComponentInfo) ProtoReflect() protoreflect.Message { // Deprecated: Use ComponentInfo.ProtoReflect.Descriptor instead. func (*ComponentInfo) Descriptor() ([]byte, []int) { - return file_proto_mcp_v1_mcp_proto_rawDescGZIP(), []int{16} + return file_proto_mcp_v1_mcp_proto_rawDescGZIP(), []int{17} } func (x *ComponentInfo) GetName() string { @@ -945,7 +1029,7 @@ type ListServicesResponse struct { func (x *ListServicesResponse) Reset() { *x = ListServicesResponse{} - mi := &file_proto_mcp_v1_mcp_proto_msgTypes[17] + mi := &file_proto_mcp_v1_mcp_proto_msgTypes[18] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -957,7 +1041,7 @@ func (x *ListServicesResponse) String() string { func (*ListServicesResponse) ProtoMessage() {} func (x *ListServicesResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_mcp_v1_mcp_proto_msgTypes[17] + mi := &file_proto_mcp_v1_mcp_proto_msgTypes[18] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -970,7 +1054,7 @@ func (x *ListServicesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListServicesResponse.ProtoReflect.Descriptor instead. func (*ListServicesResponse) Descriptor() ([]byte, []int) { - return file_proto_mcp_v1_mcp_proto_rawDescGZIP(), []int{17} + return file_proto_mcp_v1_mcp_proto_rawDescGZIP(), []int{18} } func (x *ListServicesResponse) GetServices() []*ServiceInfo { @@ -990,7 +1074,7 @@ type GetServiceStatusRequest struct { func (x *GetServiceStatusRequest) Reset() { *x = GetServiceStatusRequest{} - mi := &file_proto_mcp_v1_mcp_proto_msgTypes[18] + mi := &file_proto_mcp_v1_mcp_proto_msgTypes[19] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1002,7 +1086,7 @@ func (x *GetServiceStatusRequest) String() string { func (*GetServiceStatusRequest) ProtoMessage() {} func (x *GetServiceStatusRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_mcp_v1_mcp_proto_msgTypes[18] + mi := &file_proto_mcp_v1_mcp_proto_msgTypes[19] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1015,7 +1099,7 @@ func (x *GetServiceStatusRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetServiceStatusRequest.ProtoReflect.Descriptor instead. func (*GetServiceStatusRequest) Descriptor() ([]byte, []int) { - return file_proto_mcp_v1_mcp_proto_rawDescGZIP(), []int{18} + return file_proto_mcp_v1_mcp_proto_rawDescGZIP(), []int{19} } func (x *GetServiceStatusRequest) GetName() string { @@ -1037,7 +1121,7 @@ type DriftInfo struct { func (x *DriftInfo) Reset() { *x = DriftInfo{} - mi := &file_proto_mcp_v1_mcp_proto_msgTypes[19] + mi := &file_proto_mcp_v1_mcp_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1049,7 +1133,7 @@ func (x *DriftInfo) String() string { func (*DriftInfo) ProtoMessage() {} func (x *DriftInfo) ProtoReflect() protoreflect.Message { - mi := &file_proto_mcp_v1_mcp_proto_msgTypes[19] + mi := &file_proto_mcp_v1_mcp_proto_msgTypes[20] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1062,7 +1146,7 @@ func (x *DriftInfo) ProtoReflect() protoreflect.Message { // Deprecated: Use DriftInfo.ProtoReflect.Descriptor instead. func (*DriftInfo) Descriptor() ([]byte, []int) { - return file_proto_mcp_v1_mcp_proto_rawDescGZIP(), []int{19} + return file_proto_mcp_v1_mcp_proto_rawDescGZIP(), []int{20} } func (x *DriftInfo) GetService() string { @@ -1106,7 +1190,7 @@ type EventInfo struct { func (x *EventInfo) Reset() { *x = EventInfo{} - mi := &file_proto_mcp_v1_mcp_proto_msgTypes[20] + mi := &file_proto_mcp_v1_mcp_proto_msgTypes[21] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1118,7 +1202,7 @@ func (x *EventInfo) String() string { func (*EventInfo) ProtoMessage() {} func (x *EventInfo) ProtoReflect() protoreflect.Message { - mi := &file_proto_mcp_v1_mcp_proto_msgTypes[20] + mi := &file_proto_mcp_v1_mcp_proto_msgTypes[21] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1131,7 +1215,7 @@ func (x *EventInfo) ProtoReflect() protoreflect.Message { // Deprecated: Use EventInfo.ProtoReflect.Descriptor instead. func (*EventInfo) Descriptor() ([]byte, []int) { - return file_proto_mcp_v1_mcp_proto_rawDescGZIP(), []int{20} + return file_proto_mcp_v1_mcp_proto_rawDescGZIP(), []int{21} } func (x *EventInfo) GetService() string { @@ -1180,7 +1264,7 @@ type GetServiceStatusResponse struct { func (x *GetServiceStatusResponse) Reset() { *x = GetServiceStatusResponse{} - mi := &file_proto_mcp_v1_mcp_proto_msgTypes[21] + mi := &file_proto_mcp_v1_mcp_proto_msgTypes[22] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1192,7 +1276,7 @@ func (x *GetServiceStatusResponse) String() string { func (*GetServiceStatusResponse) ProtoMessage() {} func (x *GetServiceStatusResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_mcp_v1_mcp_proto_msgTypes[21] + mi := &file_proto_mcp_v1_mcp_proto_msgTypes[22] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1205,7 +1289,7 @@ func (x *GetServiceStatusResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetServiceStatusResponse.ProtoReflect.Descriptor instead. func (*GetServiceStatusResponse) Descriptor() ([]byte, []int) { - return file_proto_mcp_v1_mcp_proto_rawDescGZIP(), []int{21} + return file_proto_mcp_v1_mcp_proto_rawDescGZIP(), []int{22} } func (x *GetServiceStatusResponse) GetServices() []*ServiceInfo { @@ -1237,7 +1321,7 @@ type LiveCheckRequest struct { func (x *LiveCheckRequest) Reset() { *x = LiveCheckRequest{} - mi := &file_proto_mcp_v1_mcp_proto_msgTypes[22] + mi := &file_proto_mcp_v1_mcp_proto_msgTypes[23] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1249,7 +1333,7 @@ func (x *LiveCheckRequest) String() string { func (*LiveCheckRequest) ProtoMessage() {} func (x *LiveCheckRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_mcp_v1_mcp_proto_msgTypes[22] + mi := &file_proto_mcp_v1_mcp_proto_msgTypes[23] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1262,7 +1346,7 @@ func (x *LiveCheckRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use LiveCheckRequest.ProtoReflect.Descriptor instead. func (*LiveCheckRequest) Descriptor() ([]byte, []int) { - return file_proto_mcp_v1_mcp_proto_rawDescGZIP(), []int{22} + return file_proto_mcp_v1_mcp_proto_rawDescGZIP(), []int{23} } type LiveCheckResponse struct { @@ -1275,7 +1359,7 @@ type LiveCheckResponse struct { func (x *LiveCheckResponse) Reset() { *x = LiveCheckResponse{} - mi := &file_proto_mcp_v1_mcp_proto_msgTypes[23] + mi := &file_proto_mcp_v1_mcp_proto_msgTypes[24] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1287,7 +1371,7 @@ func (x *LiveCheckResponse) String() string { func (*LiveCheckResponse) ProtoMessage() {} func (x *LiveCheckResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_mcp_v1_mcp_proto_msgTypes[23] + mi := &file_proto_mcp_v1_mcp_proto_msgTypes[24] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1300,7 +1384,7 @@ func (x *LiveCheckResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use LiveCheckResponse.ProtoReflect.Descriptor instead. func (*LiveCheckResponse) Descriptor() ([]byte, []int) { - return file_proto_mcp_v1_mcp_proto_rawDescGZIP(), []int{23} + return file_proto_mcp_v1_mcp_proto_rawDescGZIP(), []int{24} } func (x *LiveCheckResponse) GetServices() []*ServiceInfo { @@ -1320,7 +1404,7 @@ type AdoptContainersRequest struct { func (x *AdoptContainersRequest) Reset() { *x = AdoptContainersRequest{} - mi := &file_proto_mcp_v1_mcp_proto_msgTypes[24] + mi := &file_proto_mcp_v1_mcp_proto_msgTypes[25] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1332,7 +1416,7 @@ func (x *AdoptContainersRequest) String() string { func (*AdoptContainersRequest) ProtoMessage() {} func (x *AdoptContainersRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_mcp_v1_mcp_proto_msgTypes[24] + mi := &file_proto_mcp_v1_mcp_proto_msgTypes[25] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1345,7 +1429,7 @@ func (x *AdoptContainersRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use AdoptContainersRequest.ProtoReflect.Descriptor instead. func (*AdoptContainersRequest) Descriptor() ([]byte, []int) { - return file_proto_mcp_v1_mcp_proto_rawDescGZIP(), []int{24} + return file_proto_mcp_v1_mcp_proto_rawDescGZIP(), []int{25} } func (x *AdoptContainersRequest) GetService() string { @@ -1369,7 +1453,7 @@ type AdoptResult struct { func (x *AdoptResult) Reset() { *x = AdoptResult{} - mi := &file_proto_mcp_v1_mcp_proto_msgTypes[25] + mi := &file_proto_mcp_v1_mcp_proto_msgTypes[26] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1381,7 +1465,7 @@ func (x *AdoptResult) String() string { func (*AdoptResult) ProtoMessage() {} func (x *AdoptResult) ProtoReflect() protoreflect.Message { - mi := &file_proto_mcp_v1_mcp_proto_msgTypes[25] + mi := &file_proto_mcp_v1_mcp_proto_msgTypes[26] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1394,7 +1478,7 @@ func (x *AdoptResult) ProtoReflect() protoreflect.Message { // Deprecated: Use AdoptResult.ProtoReflect.Descriptor instead. func (*AdoptResult) Descriptor() ([]byte, []int) { - return file_proto_mcp_v1_mcp_proto_rawDescGZIP(), []int{25} + return file_proto_mcp_v1_mcp_proto_rawDescGZIP(), []int{26} } func (x *AdoptResult) GetContainer() string { @@ -1434,7 +1518,7 @@ type AdoptContainersResponse struct { func (x *AdoptContainersResponse) Reset() { *x = AdoptContainersResponse{} - mi := &file_proto_mcp_v1_mcp_proto_msgTypes[26] + mi := &file_proto_mcp_v1_mcp_proto_msgTypes[27] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1446,7 +1530,7 @@ func (x *AdoptContainersResponse) String() string { func (*AdoptContainersResponse) ProtoMessage() {} func (x *AdoptContainersResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_mcp_v1_mcp_proto_msgTypes[26] + mi := &file_proto_mcp_v1_mcp_proto_msgTypes[27] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1459,7 +1543,7 @@ func (x *AdoptContainersResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use AdoptContainersResponse.ProtoReflect.Descriptor instead. func (*AdoptContainersResponse) Descriptor() ([]byte, []int) { - return file_proto_mcp_v1_mcp_proto_rawDescGZIP(), []int{26} + return file_proto_mcp_v1_mcp_proto_rawDescGZIP(), []int{27} } func (x *AdoptContainersResponse) GetResults() []*AdoptResult { @@ -1483,7 +1567,7 @@ type PushFileRequest struct { func (x *PushFileRequest) Reset() { *x = PushFileRequest{} - mi := &file_proto_mcp_v1_mcp_proto_msgTypes[27] + mi := &file_proto_mcp_v1_mcp_proto_msgTypes[28] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1495,7 +1579,7 @@ func (x *PushFileRequest) String() string { func (*PushFileRequest) ProtoMessage() {} func (x *PushFileRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_mcp_v1_mcp_proto_msgTypes[27] + mi := &file_proto_mcp_v1_mcp_proto_msgTypes[28] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1508,7 +1592,7 @@ func (x *PushFileRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use PushFileRequest.ProtoReflect.Descriptor instead. func (*PushFileRequest) Descriptor() ([]byte, []int) { - return file_proto_mcp_v1_mcp_proto_rawDescGZIP(), []int{27} + return file_proto_mcp_v1_mcp_proto_rawDescGZIP(), []int{28} } func (x *PushFileRequest) GetService() string { @@ -1549,7 +1633,7 @@ type PushFileResponse struct { func (x *PushFileResponse) Reset() { *x = PushFileResponse{} - mi := &file_proto_mcp_v1_mcp_proto_msgTypes[28] + mi := &file_proto_mcp_v1_mcp_proto_msgTypes[29] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1561,7 +1645,7 @@ func (x *PushFileResponse) String() string { func (*PushFileResponse) ProtoMessage() {} func (x *PushFileResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_mcp_v1_mcp_proto_msgTypes[28] + mi := &file_proto_mcp_v1_mcp_proto_msgTypes[29] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1574,7 +1658,7 @@ func (x *PushFileResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use PushFileResponse.ProtoReflect.Descriptor instead. func (*PushFileResponse) Descriptor() ([]byte, []int) { - return file_proto_mcp_v1_mcp_proto_rawDescGZIP(), []int{28} + return file_proto_mcp_v1_mcp_proto_rawDescGZIP(), []int{29} } func (x *PushFileResponse) GetSuccess() bool { @@ -1602,7 +1686,7 @@ type PullFileRequest struct { func (x *PullFileRequest) Reset() { *x = PullFileRequest{} - mi := &file_proto_mcp_v1_mcp_proto_msgTypes[29] + mi := &file_proto_mcp_v1_mcp_proto_msgTypes[30] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1614,7 +1698,7 @@ func (x *PullFileRequest) String() string { func (*PullFileRequest) ProtoMessage() {} func (x *PullFileRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_mcp_v1_mcp_proto_msgTypes[29] + mi := &file_proto_mcp_v1_mcp_proto_msgTypes[30] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1627,7 +1711,7 @@ func (x *PullFileRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use PullFileRequest.ProtoReflect.Descriptor instead. func (*PullFileRequest) Descriptor() ([]byte, []int) { - return file_proto_mcp_v1_mcp_proto_rawDescGZIP(), []int{29} + return file_proto_mcp_v1_mcp_proto_rawDescGZIP(), []int{30} } func (x *PullFileRequest) GetService() string { @@ -1655,7 +1739,7 @@ type PullFileResponse struct { func (x *PullFileResponse) Reset() { *x = PullFileResponse{} - mi := &file_proto_mcp_v1_mcp_proto_msgTypes[30] + mi := &file_proto_mcp_v1_mcp_proto_msgTypes[31] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1667,7 +1751,7 @@ func (x *PullFileResponse) String() string { func (*PullFileResponse) ProtoMessage() {} func (x *PullFileResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_mcp_v1_mcp_proto_msgTypes[30] + mi := &file_proto_mcp_v1_mcp_proto_msgTypes[31] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1680,7 +1764,7 @@ func (x *PullFileResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use PullFileResponse.ProtoReflect.Descriptor instead. func (*PullFileResponse) Descriptor() ([]byte, []int) { - return file_proto_mcp_v1_mcp_proto_rawDescGZIP(), []int{30} + return file_proto_mcp_v1_mcp_proto_rawDescGZIP(), []int{31} } func (x *PullFileResponse) GetContent() []byte { @@ -1712,7 +1796,7 @@ type NodeStatusRequest struct { func (x *NodeStatusRequest) Reset() { *x = NodeStatusRequest{} - mi := &file_proto_mcp_v1_mcp_proto_msgTypes[31] + mi := &file_proto_mcp_v1_mcp_proto_msgTypes[32] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1724,7 +1808,7 @@ func (x *NodeStatusRequest) String() string { func (*NodeStatusRequest) ProtoMessage() {} func (x *NodeStatusRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_mcp_v1_mcp_proto_msgTypes[31] + mi := &file_proto_mcp_v1_mcp_proto_msgTypes[32] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1737,7 +1821,7 @@ func (x *NodeStatusRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use NodeStatusRequest.ProtoReflect.Descriptor instead. func (*NodeStatusRequest) Descriptor() ([]byte, []int) { - return file_proto_mcp_v1_mcp_proto_rawDescGZIP(), []int{31} + return file_proto_mcp_v1_mcp_proto_rawDescGZIP(), []int{32} } type NodeStatusResponse struct { @@ -1760,7 +1844,7 @@ type NodeStatusResponse struct { func (x *NodeStatusResponse) Reset() { *x = NodeStatusResponse{} - mi := &file_proto_mcp_v1_mcp_proto_msgTypes[32] + mi := &file_proto_mcp_v1_mcp_proto_msgTypes[33] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1772,7 +1856,7 @@ func (x *NodeStatusResponse) String() string { func (*NodeStatusResponse) ProtoMessage() {} func (x *NodeStatusResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_mcp_v1_mcp_proto_msgTypes[32] + mi := &file_proto_mcp_v1_mcp_proto_msgTypes[33] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1785,7 +1869,7 @@ func (x *NodeStatusResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use NodeStatusResponse.ProtoReflect.Descriptor instead. func (*NodeStatusResponse) Descriptor() ([]byte, []int) { - return file_proto_mcp_v1_mcp_proto_rawDescGZIP(), []int{32} + return file_proto_mcp_v1_mcp_proto_rawDescGZIP(), []int{33} } func (x *NodeStatusResponse) GetNodeName() string { @@ -1882,7 +1966,7 @@ type PurgeRequest struct { func (x *PurgeRequest) Reset() { *x = PurgeRequest{} - mi := &file_proto_mcp_v1_mcp_proto_msgTypes[33] + mi := &file_proto_mcp_v1_mcp_proto_msgTypes[34] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1894,7 +1978,7 @@ func (x *PurgeRequest) String() string { func (*PurgeRequest) ProtoMessage() {} func (x *PurgeRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_mcp_v1_mcp_proto_msgTypes[33] + mi := &file_proto_mcp_v1_mcp_proto_msgTypes[34] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1907,7 +1991,7 @@ func (x *PurgeRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use PurgeRequest.ProtoReflect.Descriptor instead. func (*PurgeRequest) Descriptor() ([]byte, []int) { - return file_proto_mcp_v1_mcp_proto_rawDescGZIP(), []int{33} + return file_proto_mcp_v1_mcp_proto_rawDescGZIP(), []int{34} } func (x *PurgeRequest) GetService() string { @@ -1947,7 +2031,7 @@ type PurgeResponse struct { func (x *PurgeResponse) Reset() { *x = PurgeResponse{} - mi := &file_proto_mcp_v1_mcp_proto_msgTypes[34] + mi := &file_proto_mcp_v1_mcp_proto_msgTypes[35] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1959,7 +2043,7 @@ func (x *PurgeResponse) String() string { func (*PurgeResponse) ProtoMessage() {} func (x *PurgeResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_mcp_v1_mcp_proto_msgTypes[34] + mi := &file_proto_mcp_v1_mcp_proto_msgTypes[35] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1972,7 +2056,7 @@ func (x *PurgeResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use PurgeResponse.ProtoReflect.Descriptor instead. func (*PurgeResponse) Descriptor() ([]byte, []int) { - return file_proto_mcp_v1_mcp_proto_rawDescGZIP(), []int{34} + return file_proto_mcp_v1_mcp_proto_rawDescGZIP(), []int{35} } func (x *PurgeResponse) GetResults() []*PurgeResult { @@ -1996,7 +2080,7 @@ type PurgeResult struct { func (x *PurgeResult) Reset() { *x = PurgeResult{} - mi := &file_proto_mcp_v1_mcp_proto_msgTypes[35] + mi := &file_proto_mcp_v1_mcp_proto_msgTypes[36] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2008,7 +2092,7 @@ func (x *PurgeResult) String() string { func (*PurgeResult) ProtoMessage() {} func (x *PurgeResult) ProtoReflect() protoreflect.Message { - mi := &file_proto_mcp_v1_mcp_proto_msgTypes[35] + mi := &file_proto_mcp_v1_mcp_proto_msgTypes[36] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2021,7 +2105,7 @@ func (x *PurgeResult) ProtoReflect() protoreflect.Message { // Deprecated: Use PurgeResult.ProtoReflect.Descriptor instead. func (*PurgeResult) Descriptor() ([]byte, []int) { - return file_proto_mcp_v1_mcp_proto_rawDescGZIP(), []int{35} + return file_proto_mcp_v1_mcp_proto_rawDescGZIP(), []int{36} } func (x *PurgeResult) GetService() string { @@ -2056,7 +2140,12 @@ var File_proto_mcp_v1_mcp_proto protoreflect.FileDescriptor const file_proto_mcp_v1_mcp_proto_rawDesc = "" + "\n" + - "\x16proto/mcp/v1/mcp.proto\x12\x06mcp.v1\x1a\x1fgoogle/protobuf/timestamp.proto\"\xc3\x01\n" + + "\x16proto/mcp/v1/mcp.proto\x12\x06mcp.v1\x1a\x1fgoogle/protobuf/timestamp.proto\"c\n" + + "\tRouteSpec\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12\x12\n" + + "\x04port\x18\x02 \x01(\x05R\x04port\x12\x12\n" + + "\x04mode\x18\x03 \x01(\tR\x04mode\x12\x1a\n" + + "\bhostname\x18\x04 \x01(\tR\bhostname\"\x80\x02\n" + "\rComponentSpec\x12\x12\n" + "\x04name\x18\x01 \x01(\tR\x04name\x12\x14\n" + "\x05image\x18\x02 \x01(\tR\x05image\x12\x18\n" + @@ -2065,7 +2154,10 @@ const file_proto_mcp_v1_mcp_proto_rawDesc = "" + "\arestart\x18\x05 \x01(\tR\arestart\x12\x14\n" + "\x05ports\x18\x06 \x03(\tR\x05ports\x12\x18\n" + "\avolumes\x18\a \x03(\tR\avolumes\x12\x10\n" + - "\x03cmd\x18\b \x03(\tR\x03cmd\"p\n" + + "\x03cmd\x18\b \x03(\tR\x03cmd\x12)\n" + + "\x06routes\x18\t \x03(\v2\x11.mcp.v1.RouteSpecR\x06routes\x12\x10\n" + + "\x03env\x18\n" + + " \x03(\tR\x03env\"p\n" + "\vServiceSpec\x12\x12\n" + "\x04name\x18\x01 \x01(\tR\x04name\x12\x16\n" + "\x06active\x18\x02 \x01(\bR\x06active\x125\n" + @@ -2216,97 +2308,99 @@ func file_proto_mcp_v1_mcp_proto_rawDescGZIP() []byte { return file_proto_mcp_v1_mcp_proto_rawDescData } -var file_proto_mcp_v1_mcp_proto_msgTypes = make([]protoimpl.MessageInfo, 36) +var file_proto_mcp_v1_mcp_proto_msgTypes = make([]protoimpl.MessageInfo, 37) var file_proto_mcp_v1_mcp_proto_goTypes = []any{ - (*ComponentSpec)(nil), // 0: mcp.v1.ComponentSpec - (*ServiceSpec)(nil), // 1: mcp.v1.ServiceSpec - (*DeployRequest)(nil), // 2: mcp.v1.DeployRequest - (*DeployResponse)(nil), // 3: mcp.v1.DeployResponse - (*ComponentResult)(nil), // 4: mcp.v1.ComponentResult - (*StopServiceRequest)(nil), // 5: mcp.v1.StopServiceRequest - (*StopServiceResponse)(nil), // 6: mcp.v1.StopServiceResponse - (*StartServiceRequest)(nil), // 7: mcp.v1.StartServiceRequest - (*StartServiceResponse)(nil), // 8: mcp.v1.StartServiceResponse - (*RestartServiceRequest)(nil), // 9: mcp.v1.RestartServiceRequest - (*RestartServiceResponse)(nil), // 10: mcp.v1.RestartServiceResponse - (*SyncDesiredStateRequest)(nil), // 11: mcp.v1.SyncDesiredStateRequest - (*SyncDesiredStateResponse)(nil), // 12: mcp.v1.SyncDesiredStateResponse - (*ServiceSyncResult)(nil), // 13: mcp.v1.ServiceSyncResult - (*ListServicesRequest)(nil), // 14: mcp.v1.ListServicesRequest - (*ServiceInfo)(nil), // 15: mcp.v1.ServiceInfo - (*ComponentInfo)(nil), // 16: mcp.v1.ComponentInfo - (*ListServicesResponse)(nil), // 17: mcp.v1.ListServicesResponse - (*GetServiceStatusRequest)(nil), // 18: mcp.v1.GetServiceStatusRequest - (*DriftInfo)(nil), // 19: mcp.v1.DriftInfo - (*EventInfo)(nil), // 20: mcp.v1.EventInfo - (*GetServiceStatusResponse)(nil), // 21: mcp.v1.GetServiceStatusResponse - (*LiveCheckRequest)(nil), // 22: mcp.v1.LiveCheckRequest - (*LiveCheckResponse)(nil), // 23: mcp.v1.LiveCheckResponse - (*AdoptContainersRequest)(nil), // 24: mcp.v1.AdoptContainersRequest - (*AdoptResult)(nil), // 25: mcp.v1.AdoptResult - (*AdoptContainersResponse)(nil), // 26: mcp.v1.AdoptContainersResponse - (*PushFileRequest)(nil), // 27: mcp.v1.PushFileRequest - (*PushFileResponse)(nil), // 28: mcp.v1.PushFileResponse - (*PullFileRequest)(nil), // 29: mcp.v1.PullFileRequest - (*PullFileResponse)(nil), // 30: mcp.v1.PullFileResponse - (*NodeStatusRequest)(nil), // 31: mcp.v1.NodeStatusRequest - (*NodeStatusResponse)(nil), // 32: mcp.v1.NodeStatusResponse - (*PurgeRequest)(nil), // 33: mcp.v1.PurgeRequest - (*PurgeResponse)(nil), // 34: mcp.v1.PurgeResponse - (*PurgeResult)(nil), // 35: mcp.v1.PurgeResult - (*timestamppb.Timestamp)(nil), // 36: google.protobuf.Timestamp + (*RouteSpec)(nil), // 0: mcp.v1.RouteSpec + (*ComponentSpec)(nil), // 1: mcp.v1.ComponentSpec + (*ServiceSpec)(nil), // 2: mcp.v1.ServiceSpec + (*DeployRequest)(nil), // 3: mcp.v1.DeployRequest + (*DeployResponse)(nil), // 4: mcp.v1.DeployResponse + (*ComponentResult)(nil), // 5: mcp.v1.ComponentResult + (*StopServiceRequest)(nil), // 6: mcp.v1.StopServiceRequest + (*StopServiceResponse)(nil), // 7: mcp.v1.StopServiceResponse + (*StartServiceRequest)(nil), // 8: mcp.v1.StartServiceRequest + (*StartServiceResponse)(nil), // 9: mcp.v1.StartServiceResponse + (*RestartServiceRequest)(nil), // 10: mcp.v1.RestartServiceRequest + (*RestartServiceResponse)(nil), // 11: mcp.v1.RestartServiceResponse + (*SyncDesiredStateRequest)(nil), // 12: mcp.v1.SyncDesiredStateRequest + (*SyncDesiredStateResponse)(nil), // 13: mcp.v1.SyncDesiredStateResponse + (*ServiceSyncResult)(nil), // 14: mcp.v1.ServiceSyncResult + (*ListServicesRequest)(nil), // 15: mcp.v1.ListServicesRequest + (*ServiceInfo)(nil), // 16: mcp.v1.ServiceInfo + (*ComponentInfo)(nil), // 17: mcp.v1.ComponentInfo + (*ListServicesResponse)(nil), // 18: mcp.v1.ListServicesResponse + (*GetServiceStatusRequest)(nil), // 19: mcp.v1.GetServiceStatusRequest + (*DriftInfo)(nil), // 20: mcp.v1.DriftInfo + (*EventInfo)(nil), // 21: mcp.v1.EventInfo + (*GetServiceStatusResponse)(nil), // 22: mcp.v1.GetServiceStatusResponse + (*LiveCheckRequest)(nil), // 23: mcp.v1.LiveCheckRequest + (*LiveCheckResponse)(nil), // 24: mcp.v1.LiveCheckResponse + (*AdoptContainersRequest)(nil), // 25: mcp.v1.AdoptContainersRequest + (*AdoptResult)(nil), // 26: mcp.v1.AdoptResult + (*AdoptContainersResponse)(nil), // 27: mcp.v1.AdoptContainersResponse + (*PushFileRequest)(nil), // 28: mcp.v1.PushFileRequest + (*PushFileResponse)(nil), // 29: mcp.v1.PushFileResponse + (*PullFileRequest)(nil), // 30: mcp.v1.PullFileRequest + (*PullFileResponse)(nil), // 31: mcp.v1.PullFileResponse + (*NodeStatusRequest)(nil), // 32: mcp.v1.NodeStatusRequest + (*NodeStatusResponse)(nil), // 33: mcp.v1.NodeStatusResponse + (*PurgeRequest)(nil), // 34: mcp.v1.PurgeRequest + (*PurgeResponse)(nil), // 35: mcp.v1.PurgeResponse + (*PurgeResult)(nil), // 36: mcp.v1.PurgeResult + (*timestamppb.Timestamp)(nil), // 37: google.protobuf.Timestamp } var file_proto_mcp_v1_mcp_proto_depIdxs = []int32{ - 0, // 0: mcp.v1.ServiceSpec.components:type_name -> mcp.v1.ComponentSpec - 1, // 1: mcp.v1.DeployRequest.service:type_name -> mcp.v1.ServiceSpec - 4, // 2: mcp.v1.DeployResponse.results:type_name -> mcp.v1.ComponentResult - 4, // 3: mcp.v1.StopServiceResponse.results:type_name -> mcp.v1.ComponentResult - 4, // 4: mcp.v1.StartServiceResponse.results:type_name -> mcp.v1.ComponentResult - 4, // 5: mcp.v1.RestartServiceResponse.results:type_name -> mcp.v1.ComponentResult - 1, // 6: mcp.v1.SyncDesiredStateRequest.services:type_name -> mcp.v1.ServiceSpec - 13, // 7: mcp.v1.SyncDesiredStateResponse.results:type_name -> mcp.v1.ServiceSyncResult - 16, // 8: mcp.v1.ServiceInfo.components:type_name -> mcp.v1.ComponentInfo - 36, // 9: mcp.v1.ComponentInfo.started:type_name -> google.protobuf.Timestamp - 15, // 10: mcp.v1.ListServicesResponse.services:type_name -> mcp.v1.ServiceInfo - 36, // 11: mcp.v1.EventInfo.timestamp:type_name -> google.protobuf.Timestamp - 15, // 12: mcp.v1.GetServiceStatusResponse.services:type_name -> mcp.v1.ServiceInfo - 19, // 13: mcp.v1.GetServiceStatusResponse.drift:type_name -> mcp.v1.DriftInfo - 20, // 14: mcp.v1.GetServiceStatusResponse.recent_events:type_name -> mcp.v1.EventInfo - 15, // 15: mcp.v1.LiveCheckResponse.services:type_name -> mcp.v1.ServiceInfo - 25, // 16: mcp.v1.AdoptContainersResponse.results:type_name -> mcp.v1.AdoptResult - 36, // 17: mcp.v1.NodeStatusResponse.uptime_since:type_name -> google.protobuf.Timestamp - 35, // 18: mcp.v1.PurgeResponse.results:type_name -> mcp.v1.PurgeResult - 2, // 19: mcp.v1.McpAgentService.Deploy:input_type -> mcp.v1.DeployRequest - 5, // 20: mcp.v1.McpAgentService.StopService:input_type -> mcp.v1.StopServiceRequest - 7, // 21: mcp.v1.McpAgentService.StartService:input_type -> mcp.v1.StartServiceRequest - 9, // 22: mcp.v1.McpAgentService.RestartService:input_type -> mcp.v1.RestartServiceRequest - 11, // 23: mcp.v1.McpAgentService.SyncDesiredState:input_type -> mcp.v1.SyncDesiredStateRequest - 14, // 24: mcp.v1.McpAgentService.ListServices:input_type -> mcp.v1.ListServicesRequest - 18, // 25: mcp.v1.McpAgentService.GetServiceStatus:input_type -> mcp.v1.GetServiceStatusRequest - 22, // 26: mcp.v1.McpAgentService.LiveCheck:input_type -> mcp.v1.LiveCheckRequest - 24, // 27: mcp.v1.McpAgentService.AdoptContainers:input_type -> mcp.v1.AdoptContainersRequest - 33, // 28: mcp.v1.McpAgentService.PurgeComponent:input_type -> mcp.v1.PurgeRequest - 27, // 29: mcp.v1.McpAgentService.PushFile:input_type -> mcp.v1.PushFileRequest - 29, // 30: mcp.v1.McpAgentService.PullFile:input_type -> mcp.v1.PullFileRequest - 31, // 31: mcp.v1.McpAgentService.NodeStatus:input_type -> mcp.v1.NodeStatusRequest - 3, // 32: mcp.v1.McpAgentService.Deploy:output_type -> mcp.v1.DeployResponse - 6, // 33: mcp.v1.McpAgentService.StopService:output_type -> mcp.v1.StopServiceResponse - 8, // 34: mcp.v1.McpAgentService.StartService:output_type -> mcp.v1.StartServiceResponse - 10, // 35: mcp.v1.McpAgentService.RestartService:output_type -> mcp.v1.RestartServiceResponse - 12, // 36: mcp.v1.McpAgentService.SyncDesiredState:output_type -> mcp.v1.SyncDesiredStateResponse - 17, // 37: mcp.v1.McpAgentService.ListServices:output_type -> mcp.v1.ListServicesResponse - 21, // 38: mcp.v1.McpAgentService.GetServiceStatus:output_type -> mcp.v1.GetServiceStatusResponse - 23, // 39: mcp.v1.McpAgentService.LiveCheck:output_type -> mcp.v1.LiveCheckResponse - 26, // 40: mcp.v1.McpAgentService.AdoptContainers:output_type -> mcp.v1.AdoptContainersResponse - 34, // 41: mcp.v1.McpAgentService.PurgeComponent:output_type -> mcp.v1.PurgeResponse - 28, // 42: mcp.v1.McpAgentService.PushFile:output_type -> mcp.v1.PushFileResponse - 30, // 43: mcp.v1.McpAgentService.PullFile:output_type -> mcp.v1.PullFileResponse - 32, // 44: mcp.v1.McpAgentService.NodeStatus:output_type -> mcp.v1.NodeStatusResponse - 32, // [32:45] is the sub-list for method output_type - 19, // [19:32] is the sub-list for method input_type - 19, // [19:19] is the sub-list for extension type_name - 19, // [19:19] is the sub-list for extension extendee - 0, // [0:19] is the sub-list for field type_name + 0, // 0: mcp.v1.ComponentSpec.routes:type_name -> mcp.v1.RouteSpec + 1, // 1: mcp.v1.ServiceSpec.components:type_name -> mcp.v1.ComponentSpec + 2, // 2: mcp.v1.DeployRequest.service:type_name -> mcp.v1.ServiceSpec + 5, // 3: mcp.v1.DeployResponse.results:type_name -> mcp.v1.ComponentResult + 5, // 4: mcp.v1.StopServiceResponse.results:type_name -> mcp.v1.ComponentResult + 5, // 5: mcp.v1.StartServiceResponse.results:type_name -> mcp.v1.ComponentResult + 5, // 6: mcp.v1.RestartServiceResponse.results:type_name -> mcp.v1.ComponentResult + 2, // 7: mcp.v1.SyncDesiredStateRequest.services:type_name -> mcp.v1.ServiceSpec + 14, // 8: mcp.v1.SyncDesiredStateResponse.results:type_name -> mcp.v1.ServiceSyncResult + 17, // 9: mcp.v1.ServiceInfo.components:type_name -> mcp.v1.ComponentInfo + 37, // 10: mcp.v1.ComponentInfo.started:type_name -> google.protobuf.Timestamp + 16, // 11: mcp.v1.ListServicesResponse.services:type_name -> mcp.v1.ServiceInfo + 37, // 12: mcp.v1.EventInfo.timestamp:type_name -> google.protobuf.Timestamp + 16, // 13: mcp.v1.GetServiceStatusResponse.services:type_name -> mcp.v1.ServiceInfo + 20, // 14: mcp.v1.GetServiceStatusResponse.drift:type_name -> mcp.v1.DriftInfo + 21, // 15: mcp.v1.GetServiceStatusResponse.recent_events:type_name -> mcp.v1.EventInfo + 16, // 16: mcp.v1.LiveCheckResponse.services:type_name -> mcp.v1.ServiceInfo + 26, // 17: mcp.v1.AdoptContainersResponse.results:type_name -> mcp.v1.AdoptResult + 37, // 18: mcp.v1.NodeStatusResponse.uptime_since:type_name -> google.protobuf.Timestamp + 36, // 19: mcp.v1.PurgeResponse.results:type_name -> mcp.v1.PurgeResult + 3, // 20: mcp.v1.McpAgentService.Deploy:input_type -> mcp.v1.DeployRequest + 6, // 21: mcp.v1.McpAgentService.StopService:input_type -> mcp.v1.StopServiceRequest + 8, // 22: mcp.v1.McpAgentService.StartService:input_type -> mcp.v1.StartServiceRequest + 10, // 23: mcp.v1.McpAgentService.RestartService:input_type -> mcp.v1.RestartServiceRequest + 12, // 24: mcp.v1.McpAgentService.SyncDesiredState:input_type -> mcp.v1.SyncDesiredStateRequest + 15, // 25: mcp.v1.McpAgentService.ListServices:input_type -> mcp.v1.ListServicesRequest + 19, // 26: mcp.v1.McpAgentService.GetServiceStatus:input_type -> mcp.v1.GetServiceStatusRequest + 23, // 27: mcp.v1.McpAgentService.LiveCheck:input_type -> mcp.v1.LiveCheckRequest + 25, // 28: mcp.v1.McpAgentService.AdoptContainers:input_type -> mcp.v1.AdoptContainersRequest + 34, // 29: mcp.v1.McpAgentService.PurgeComponent:input_type -> mcp.v1.PurgeRequest + 28, // 30: mcp.v1.McpAgentService.PushFile:input_type -> mcp.v1.PushFileRequest + 30, // 31: mcp.v1.McpAgentService.PullFile:input_type -> mcp.v1.PullFileRequest + 32, // 32: mcp.v1.McpAgentService.NodeStatus:input_type -> mcp.v1.NodeStatusRequest + 4, // 33: mcp.v1.McpAgentService.Deploy:output_type -> mcp.v1.DeployResponse + 7, // 34: mcp.v1.McpAgentService.StopService:output_type -> mcp.v1.StopServiceResponse + 9, // 35: mcp.v1.McpAgentService.StartService:output_type -> mcp.v1.StartServiceResponse + 11, // 36: mcp.v1.McpAgentService.RestartService:output_type -> mcp.v1.RestartServiceResponse + 13, // 37: mcp.v1.McpAgentService.SyncDesiredState:output_type -> mcp.v1.SyncDesiredStateResponse + 18, // 38: mcp.v1.McpAgentService.ListServices:output_type -> mcp.v1.ListServicesResponse + 22, // 39: mcp.v1.McpAgentService.GetServiceStatus:output_type -> mcp.v1.GetServiceStatusResponse + 24, // 40: mcp.v1.McpAgentService.LiveCheck:output_type -> mcp.v1.LiveCheckResponse + 27, // 41: mcp.v1.McpAgentService.AdoptContainers:output_type -> mcp.v1.AdoptContainersResponse + 35, // 42: mcp.v1.McpAgentService.PurgeComponent:output_type -> mcp.v1.PurgeResponse + 29, // 43: mcp.v1.McpAgentService.PushFile:output_type -> mcp.v1.PushFileResponse + 31, // 44: mcp.v1.McpAgentService.PullFile:output_type -> mcp.v1.PullFileResponse + 33, // 45: mcp.v1.McpAgentService.NodeStatus:output_type -> mcp.v1.NodeStatusResponse + 33, // [33:46] is the sub-list for method output_type + 20, // [20:33] 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 } func init() { file_proto_mcp_v1_mcp_proto_init() } @@ -2320,7 +2414,7 @@ func file_proto_mcp_v1_mcp_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_proto_mcp_v1_mcp_proto_rawDesc), len(file_proto_mcp_v1_mcp_proto_rawDesc)), NumEnums: 0, - NumMessages: 36, + NumMessages: 37, NumExtensions: 0, NumServices: 1, }, diff --git a/internal/agent/agent.go b/internal/agent/agent.go index 370c6a4..04898dd 100644 --- a/internal/agent/agent.go +++ b/internal/agent/agent.go @@ -26,11 +26,12 @@ import ( type Agent struct { mcpv1.UnimplementedMcpAgentServiceServer - Config *config.AgentConfig - DB *sql.DB - Runtime runtime.Runtime - Monitor *monitor.Monitor - Logger *slog.Logger + Config *config.AgentConfig + DB *sql.DB + Runtime runtime.Runtime + Monitor *monitor.Monitor + Logger *slog.Logger + PortAlloc *PortAllocator } // Run starts the agent: opens the database, sets up the gRPC server with @@ -51,11 +52,12 @@ func Run(cfg *config.AgentConfig) error { mon := monitor.New(db, rt, cfg.Monitor, cfg.Agent.NodeName, logger) a := &Agent{ - Config: cfg, - DB: db, - Runtime: rt, - Monitor: mon, - Logger: logger, + Config: cfg, + DB: db, + Runtime: rt, + Monitor: mon, + Logger: logger, + PortAlloc: NewPortAllocator(), } tlsCert, err := tls.LoadX509KeyPair(cfg.Server.TLSCert, cfg.Server.TLSKey) diff --git a/internal/agent/deploy.go b/internal/agent/deploy.go index fb0692c..bad37b1 100644 --- a/internal/agent/deploy.go +++ b/internal/agent/deploy.go @@ -5,6 +5,7 @@ import ( "database/sql" "errors" "fmt" + "strings" mcpv1 "git.wntrmute.dev/kyle/mcp/gen/mcp/v1" "git.wntrmute.dev/kyle/mcp/internal/registry" @@ -58,6 +59,25 @@ func (a *Agent) deployComponent(ctx context.Context, serviceName string, cs *mcp a.Logger.Info("deploying component", "service", serviceName, "component", compName, "desired", desiredState) + // Convert proto routes to registry routes. + var regRoutes []registry.Route + for _, r := range cs.GetRoutes() { + mode := r.GetMode() + if mode == "" { + mode = "l4" + } + name := r.GetName() + if name == "" { + name = "default" + } + regRoutes = append(regRoutes, registry.Route{ + Name: name, + Port: int(r.GetPort()), + Mode: mode, + Hostname: r.GetHostname(), + }) + } + regComp := ®istry.Component{ Name: compName, Service: serviceName, @@ -70,6 +90,7 @@ func (a *Agent) deployComponent(ctx context.Context, serviceName string, cs *mcp Ports: cs.GetPorts(), Volumes: cs.GetVolumes(), Cmd: cs.GetCmd(), + Routes: regRoutes, } if err := ensureComponent(a.DB, regComp); err != nil { @@ -89,16 +110,34 @@ func (a *Agent) deployComponent(ctx context.Context, serviceName string, cs *mcp _ = a.Runtime.Stop(ctx, containerName) // may not exist yet _ = a.Runtime.Remove(ctx, containerName) // may not exist yet + // Build the container spec. If the component has routes, use route-based + // port allocation and env injection. Otherwise, fall back to legacy ports. runSpec := runtime.ContainerSpec{ Name: containerName, Image: cs.GetImage(), Network: cs.GetNetwork(), User: cs.GetUser(), Restart: cs.GetRestart(), - Ports: cs.GetPorts(), Volumes: cs.GetVolumes(), Cmd: cs.GetCmd(), + Env: cs.GetEnv(), } + + if len(regRoutes) > 0 && a.PortAlloc != nil { + ports, env, err := a.allocateRoutePorts(serviceName, compName, regRoutes) + if err != nil { + return &mcpv1.ComponentResult{ + Name: compName, + Error: fmt.Sprintf("allocate route ports: %v", err), + } + } + runSpec.Ports = ports + runSpec.Env = append(runSpec.Env, env...) + } else { + // Legacy: use ports directly from the spec. + runSpec.Ports = cs.GetPorts() + } + if err := a.Runtime.Run(ctx, runSpec); err != nil { _ = registry.UpdateComponentState(a.DB, serviceName, compName, "", "removed") return &mcpv1.ComponentResult{ @@ -117,6 +156,36 @@ func (a *Agent) deployComponent(ctx context.Context, serviceName string, cs *mcp } } +// allocateRoutePorts allocates host ports for each route, stores them in +// the registry, and returns the port mappings and env vars for the container. +func (a *Agent) allocateRoutePorts(service, component string, routes []registry.Route) ([]string, []string, error) { + var ports []string + var env []string + + for _, r := range routes { + hostPort, err := a.PortAlloc.Allocate() + if err != nil { + return nil, nil, fmt.Errorf("allocate port for route %q: %w", r.Name, err) + } + + if err := registry.UpdateRouteHostPort(a.DB, service, component, r.Name, hostPort); err != nil { + a.PortAlloc.Release(hostPort) + return nil, nil, fmt.Errorf("store host port for route %q: %w", r.Name, err) + } + + ports = append(ports, fmt.Sprintf("127.0.0.1:%d:%d", hostPort, r.Port)) + + if len(routes) == 1 { + env = append(env, fmt.Sprintf("PORT=%d", hostPort)) + } else { + envName := "PORT_" + strings.ToUpper(r.Name) + env = append(env, fmt.Sprintf("%s=%d", envName, hostPort)) + } + } + + return ports, env, nil +} + // ensureService creates the service if it does not exist, or updates its // active flag if it does. func ensureService(db *sql.DB, name string, active bool) error { diff --git a/internal/agent/portalloc.go b/internal/agent/portalloc.go new file mode 100644 index 0000000..1cb5855 --- /dev/null +++ b/internal/agent/portalloc.go @@ -0,0 +1,69 @@ +package agent + +import ( + "fmt" + "math/rand/v2" + "net" + "sync" +) + +const ( + portRangeMin = 10000 + portRangeMax = 60000 + maxRetries = 10 +) + +// PortAllocator manages host port allocation for route-based deployments. +// It tracks allocated ports within the agent session to avoid double-allocation. +type PortAllocator struct { + mu sync.Mutex + allocated map[int]bool +} + +// NewPortAllocator creates a new PortAllocator. +func NewPortAllocator() *PortAllocator { + return &PortAllocator{ + allocated: make(map[int]bool), + } +} + +// Allocate picks a free port in range [10000, 60000). +// It tries random ports, checks availability with net.Listen, and retries up to 10 times. +func (pa *PortAllocator) Allocate() (int, error) { + pa.mu.Lock() + defer pa.mu.Unlock() + + for i := range maxRetries { + port := portRangeMin + rand.IntN(portRangeMax-portRangeMin) + if pa.allocated[port] { + continue + } + + if !isPortFree(port) { + continue + } + + pa.allocated[port] = true + return port, nil + _ = i + } + + return 0, fmt.Errorf("failed to allocate port after %d attempts", maxRetries) +} + +// Release marks a port as available again. +func (pa *PortAllocator) Release(port int) { + pa.mu.Lock() + defer pa.mu.Unlock() + delete(pa.allocated, port) +} + +// isPortFree checks if a TCP port is available by attempting to listen on it. +func isPortFree(port int) bool { + ln, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", port)) + if err != nil { + return false + } + _ = ln.Close() + return true +} diff --git a/internal/agent/portalloc_test.go b/internal/agent/portalloc_test.go new file mode 100644 index 0000000..65dd071 --- /dev/null +++ b/internal/agent/portalloc_test.go @@ -0,0 +1,65 @@ +package agent + +import ( + "testing" +) + +func TestPortAllocator_Allocate(t *testing.T) { + pa := NewPortAllocator() + + port, err := pa.Allocate() + if err != nil { + t.Fatalf("allocate: %v", err) + } + if port < portRangeMin || port >= portRangeMax { + t.Fatalf("port %d out of range [%d, %d)", port, portRangeMin, portRangeMax) + } +} + +func TestPortAllocator_NoDuplicates(t *testing.T) { + pa := NewPortAllocator() + + ports := make(map[int]bool) + for range 20 { + port, err := pa.Allocate() + if err != nil { + t.Fatalf("allocate: %v", err) + } + if ports[port] { + t.Fatalf("duplicate port allocated: %d", port) + } + ports[port] = true + } +} + +func TestPortAllocator_Release(t *testing.T) { + pa := NewPortAllocator() + + port, err := pa.Allocate() + if err != nil { + t.Fatalf("allocate: %v", err) + } + + pa.Release(port) + + // After release, the port should no longer be tracked as allocated. + pa.mu.Lock() + if pa.allocated[port] { + t.Fatal("port should not be tracked after release") + } + pa.mu.Unlock() +} + +func TestPortAllocator_PortIsFree(t *testing.T) { + pa := NewPortAllocator() + + port, err := pa.Allocate() + if err != nil { + t.Fatalf("allocate: %v", err) + } + + // The port should be free (we only track it, we don't hold the listener). + if !isPortFree(port) { + t.Fatalf("allocated port %d should be free on the system", port) + } +} diff --git a/internal/registry/components.go b/internal/registry/components.go index f9c6c45..b5ba364 100644 --- a/internal/registry/components.go +++ b/internal/registry/components.go @@ -6,6 +6,15 @@ import ( "time" ) +// Route represents a route entry for a component in the registry. +type Route struct { + Name string + Port int + Mode string + Hostname string + HostPort int // agent-assigned host port (0 = not yet allocated) +} + // Component represents a component in the registry. type Component struct { Name string @@ -20,6 +29,7 @@ type Component struct { Ports []string Volumes []string Cmd []string + Routes []Route CreatedAt time.Time UpdatedAt time.Time } @@ -51,6 +61,9 @@ func CreateComponent(db *sql.DB, c *Component) error { if err := setCmd(tx, c.Service, c.Name, c.Cmd); err != nil { return err } + if err := setRoutes(tx, c.Service, c.Name, c.Routes); err != nil { + return err + } return tx.Commit() } @@ -84,6 +97,10 @@ func GetComponent(db *sql.DB, service, name string) (*Component, error) { if err != nil { return nil, err } + c.Routes, err = getRoutes(db, service, name) + if err != nil { + return nil, err + } return c, nil } @@ -115,6 +132,7 @@ func ListComponents(db *sql.DB, service string) ([]Component, error) { c.Ports, _ = getPorts(db, c.Service, c.Name) c.Volumes, _ = getVolumes(db, c.Service, c.Name) c.Cmd, _ = getCmd(db, c.Service, c.Name) + c.Routes, _ = getRoutes(db, c.Service, c.Name) components = append(components, c) } @@ -168,6 +186,9 @@ func UpdateComponentSpec(db *sql.DB, c *Component) error { if err := setCmd(tx, c.Service, c.Name, c.Cmd); err != nil { return err } + if err := setRoutes(tx, c.Service, c.Name, c.Routes); err != nil { + return err + } return tx.Commit() } @@ -274,3 +295,85 @@ func getCmd(db *sql.DB, service, component string) ([]string, error) { } return cmd, rows.Err() } + +// helper: set route definitions (delete + re-insert) +func setRoutes(tx *sql.Tx, service, component string, routes []Route) error { + if _, err := tx.Exec("DELETE FROM component_routes WHERE service = ? AND component = ?", service, component); err != nil { + return fmt.Errorf("clear routes %q/%q: %w", service, component, err) + } + for _, r := range routes { + mode := r.Mode + if mode == "" { + mode = "l4" + } + name := r.Name + if name == "" { + name = "default" + } + if _, err := tx.Exec( + "INSERT INTO component_routes (service, component, name, port, mode, hostname, host_port) VALUES (?, ?, ?, ?, ?, ?, ?)", + service, component, name, r.Port, mode, r.Hostname, r.HostPort, + ); err != nil { + return fmt.Errorf("insert route %q/%q %q: %w", service, component, name, err) + } + } + return nil +} + +func getRoutes(db *sql.DB, service, component string) ([]Route, error) { + rows, err := db.Query( + "SELECT name, port, mode, hostname, host_port FROM component_routes WHERE service = ? AND component = ? ORDER BY name", + service, component, + ) + if err != nil { + return nil, fmt.Errorf("get routes %q/%q: %w", service, component, err) + } + defer func() { _ = rows.Close() }() + var routes []Route + for rows.Next() { + var r Route + if err := rows.Scan(&r.Name, &r.Port, &r.Mode, &r.Hostname, &r.HostPort); err != nil { + return nil, err + } + routes = append(routes, r) + } + return routes, rows.Err() +} + +// UpdateRouteHostPort updates the agent-assigned host port for a specific route. +func UpdateRouteHostPort(db *sql.DB, service, component, routeName string, hostPort int) error { + res, err := db.Exec( + "UPDATE component_routes SET host_port = ? WHERE service = ? AND component = ? AND name = ?", + hostPort, service, component, routeName, + ) + if err != nil { + return fmt.Errorf("update route host_port %q/%q/%q: %w", service, component, routeName, err) + } + n, _ := res.RowsAffected() + if n == 0 { + return fmt.Errorf("update route host_port %q/%q/%q: %w", service, component, routeName, sql.ErrNoRows) + } + return nil +} + +// GetRouteHostPorts returns a map of route name to assigned host port for a component. +func GetRouteHostPorts(db *sql.DB, service, component string) (map[string]int, error) { + rows, err := db.Query( + "SELECT name, host_port FROM component_routes WHERE service = ? AND component = ?", + service, component, + ) + if err != nil { + return nil, fmt.Errorf("get route host ports %q/%q: %w", service, component, err) + } + defer func() { _ = rows.Close() }() + result := make(map[string]int) + for rows.Next() { + var name string + var port int + if err := rows.Scan(&name, &port); err != nil { + return nil, err + } + result[name] = port + } + return result, rows.Err() +} diff --git a/internal/registry/db.go b/internal/registry/db.go index 4c90b39..cbc7b13 100644 --- a/internal/registry/db.go +++ b/internal/registry/db.go @@ -127,4 +127,19 @@ var migrations = []string{ CREATE INDEX IF NOT EXISTS idx_events_component_time ON events(service, component, timestamp); `, + + // Migration 2: component routes + ` + CREATE TABLE IF NOT EXISTS component_routes ( + service TEXT NOT NULL, + component TEXT NOT NULL, + name TEXT NOT NULL, + port INTEGER NOT NULL, + mode TEXT NOT NULL DEFAULT 'l4', + hostname TEXT NOT NULL DEFAULT '', + host_port INTEGER NOT NULL DEFAULT 0, + PRIMARY KEY (service, component, name), + FOREIGN KEY (service, component) REFERENCES components(service, name) ON DELETE CASCADE + ); + `, } diff --git a/internal/registry/registry_test.go b/internal/registry/registry_test.go index 50e8042..3d9b32d 100644 --- a/internal/registry/registry_test.go +++ b/internal/registry/registry_test.go @@ -237,6 +237,160 @@ func TestCascadeDelete(t *testing.T) { } } +func TestComponentRoutes(t *testing.T) { + db := openTestDB(t) + if err := CreateService(db, "svc", true); err != nil { + t.Fatalf("create service: %v", err) + } + + // Create component with routes + c := &Component{ + Name: "api", + Service: "svc", + Image: "img:v1", + Restart: "unless-stopped", + DesiredState: "running", + ObservedState: "unknown", + Routes: []Route{ + {Name: "rest", Port: 8443, Mode: "l7", Hostname: "api.example.com"}, + {Name: "grpc", Port: 9443, Mode: "l4"}, + }, + } + if err := CreateComponent(db, c); err != nil { + t.Fatalf("create component: %v", err) + } + + // Get and verify routes + got, err := GetComponent(db, "svc", "api") + if err != nil { + t.Fatalf("get: %v", err) + } + if len(got.Routes) != 2 { + t.Fatalf("routes: got %d, want 2", len(got.Routes)) + } + // Routes are ordered by name: grpc, rest + if got.Routes[0].Name != "grpc" || got.Routes[0].Port != 9443 || got.Routes[0].Mode != "l4" { + t.Fatalf("route[0]: got %+v", got.Routes[0]) + } + if got.Routes[1].Name != "rest" || got.Routes[1].Port != 8443 || got.Routes[1].Mode != "l7" || got.Routes[1].Hostname != "api.example.com" { + t.Fatalf("route[1]: got %+v", got.Routes[1]) + } + + // Update routes via UpdateComponentSpec + c.Routes = []Route{{Name: "http", Port: 8080, Mode: "l7"}} + if err := UpdateComponentSpec(db, c); err != nil { + t.Fatalf("update spec: %v", err) + } + got, _ = GetComponent(db, "svc", "api") + if len(got.Routes) != 1 || got.Routes[0].Name != "http" { + t.Fatalf("updated routes: got %+v", got.Routes) + } + + // List components includes routes + comps, err := ListComponents(db, "svc") + if err != nil { + t.Fatalf("list: %v", err) + } + if len(comps) != 1 || len(comps[0].Routes) != 1 { + t.Fatalf("list routes: got %d components, %d routes", len(comps), len(comps[0].Routes)) + } +} + +func TestRouteHostPort(t *testing.T) { + db := openTestDB(t) + if err := CreateService(db, "svc", true); err != nil { + t.Fatalf("create service: %v", err) + } + + c := &Component{ + Name: "api", + Service: "svc", + Image: "img:v1", + Restart: "unless-stopped", + DesiredState: "running", + ObservedState: "unknown", + Routes: []Route{ + {Name: "rest", Port: 8443, Mode: "l7"}, + {Name: "grpc", Port: 9443, Mode: "l4"}, + }, + } + if err := CreateComponent(db, c); err != nil { + t.Fatalf("create component: %v", err) + } + + // Initially host_port is 0 + ports, err := GetRouteHostPorts(db, "svc", "api") + if err != nil { + t.Fatalf("get host ports: %v", err) + } + if ports["rest"] != 0 || ports["grpc"] != 0 { + t.Fatalf("initial host ports should be 0: %+v", ports) + } + + // Update host ports + if err := UpdateRouteHostPort(db, "svc", "api", "rest", 12345); err != nil { + t.Fatalf("update rest: %v", err) + } + if err := UpdateRouteHostPort(db, "svc", "api", "grpc", 12346); err != nil { + t.Fatalf("update grpc: %v", err) + } + + ports, _ = GetRouteHostPorts(db, "svc", "api") + if ports["rest"] != 12345 { + t.Fatalf("rest host_port: got %d, want 12345", ports["rest"]) + } + if ports["grpc"] != 12346 { + t.Fatalf("grpc host_port: got %d, want 12346", ports["grpc"]) + } + + // Verify host_port is visible via GetComponent + got, _ := GetComponent(db, "svc", "api") + for _, r := range got.Routes { + if r.Name == "rest" && r.HostPort != 12345 { + t.Fatalf("GetComponent rest host_port: got %d", r.HostPort) + } + if r.Name == "grpc" && r.HostPort != 12346 { + t.Fatalf("GetComponent grpc host_port: got %d", r.HostPort) + } + } + + // Update nonexistent route should fail + err = UpdateRouteHostPort(db, "svc", "api", "nonexistent", 99999) + if err == nil { + t.Fatal("expected error updating nonexistent route") + } +} + +func TestRouteCascadeDelete(t *testing.T) { + db := openTestDB(t) + if err := CreateService(db, "svc", true); err != nil { + t.Fatalf("create service: %v", err) + } + + c := &Component{ + Name: "api", Service: "svc", Image: "img:v1", + Restart: "unless-stopped", DesiredState: "running", ObservedState: "unknown", + Routes: []Route{{Name: "rest", Port: 8443, Mode: "l4"}}, + } + if err := CreateComponent(db, c); err != nil { + t.Fatalf("create component: %v", err) + } + + // Delete service cascades to routes + if err := DeleteService(db, "svc"); err != nil { + t.Fatalf("delete service: %v", err) + } + + // Routes table should be empty + ports, err := GetRouteHostPorts(db, "svc", "api") + if err != nil { + t.Fatalf("get routes after cascade: %v", err) + } + if len(ports) != 0 { + t.Fatalf("routes should be empty after cascade, got %d", len(ports)) + } +} + func TestEvents(t *testing.T) { db := openTestDB(t) diff --git a/internal/runtime/podman.go b/internal/runtime/podman.go index dbea148..883c4e4 100644 --- a/internal/runtime/podman.go +++ b/internal/runtime/podman.go @@ -49,6 +49,9 @@ func (p *Podman) BuildRunArgs(spec ContainerSpec) []string { for _, vol := range spec.Volumes { args = append(args, "-v", vol) } + for _, env := range spec.Env { + args = append(args, "-e", env) + } args = append(args, spec.Image) args = append(args, spec.Cmd...) diff --git a/internal/runtime/runtime.go b/internal/runtime/runtime.go index 0a65f6a..9d8ce25 100644 --- a/internal/runtime/runtime.go +++ b/internal/runtime/runtime.go @@ -16,6 +16,7 @@ type ContainerSpec struct { Ports []string // "host:container" port mappings Volumes []string // "host:container" volume mounts Cmd []string // command and arguments + Env []string // environment variables (KEY=VALUE) } // ContainerInfo describes the observed state of a running or stopped container. diff --git a/internal/runtime/runtime_test.go b/internal/runtime/runtime_test.go index 5ebbd0b..89dbbed 100644 --- a/internal/runtime/runtime_test.go +++ b/internal/runtime/runtime_test.go @@ -76,6 +76,38 @@ func TestBuildRunArgs(t *testing.T) { }) }) + t.Run("env vars", func(t *testing.T) { + spec := ContainerSpec{ + Name: "test-app", + Image: "img:latest", + Env: []string{"PORT=12345", "PORT_GRPC=12346"}, + } + requireEqualArgs(t, p.BuildRunArgs(spec), []string{ + "run", "-d", "--name", "test-app", + "-e", "PORT=12345", "-e", "PORT_GRPC=12346", + "img:latest", + }) + }) + + t.Run("full spec with env", func(t *testing.T) { + spec := ContainerSpec{ + Name: "svc-api", + Image: "img:latest", + Network: "net", + Ports: []string{"127.0.0.1:12345:8443"}, + Volumes: []string{"/srv:/srv"}, + Env: []string{"PORT=12345"}, + } + requireEqualArgs(t, p.BuildRunArgs(spec), []string{ + "run", "-d", "--name", "svc-api", + "--network", "net", + "-p", "127.0.0.1:12345:8443", + "-v", "/srv:/srv", + "-e", "PORT=12345", + "img:latest", + }) + }) + t.Run("cmd after image", func(t *testing.T) { spec := ContainerSpec{ Name: "test-app", diff --git a/internal/servicedef/servicedef.go b/internal/servicedef/servicedef.go index 11b12d7..cef5ef7 100644 --- a/internal/servicedef/servicedef.go +++ b/internal/servicedef/servicedef.go @@ -21,16 +21,27 @@ type ServiceDef struct { Components []ComponentDef `toml:"components"` } +// RouteDef describes a route for a component, used for automatic port +// allocation and mc-proxy integration. +type RouteDef struct { + Name string `toml:"name,omitempty"` + Port int `toml:"port"` + Mode string `toml:"mode,omitempty"` + Hostname string `toml:"hostname,omitempty"` +} + // ComponentDef describes a single container component within a service. type ComponentDef struct { - Name string `toml:"name"` - Image string `toml:"image"` - Network string `toml:"network,omitempty"` - User string `toml:"user,omitempty"` - Restart string `toml:"restart,omitempty"` - Ports []string `toml:"ports,omitempty"` - Volumes []string `toml:"volumes,omitempty"` - Cmd []string `toml:"cmd,omitempty"` + Name string `toml:"name"` + Image string `toml:"image"` + Network string `toml:"network,omitempty"` + User string `toml:"user,omitempty"` + Restart string `toml:"restart,omitempty"` + Ports []string `toml:"ports,omitempty"` + Volumes []string `toml:"volumes,omitempty"` + Cmd []string `toml:"cmd,omitempty"` + Routes []RouteDef `toml:"routes,omitempty"` + Env []string `toml:"env,omitempty"` } // Load reads and parses a TOML service definition file. If the active field @@ -129,11 +140,46 @@ func validate(def *ServiceDef) error { return fmt.Errorf("duplicate component name %q in service %q", c.Name, def.Name) } seen[c.Name] = true + + if err := validateRoutes(c.Name, def.Name, c.Routes); err != nil { + return err + } } return nil } +// validateRoutes checks that routes within a component are valid. +func validateRoutes(compName, svcName string, routes []RouteDef) error { + if len(routes) == 0 { + return nil + } + + routeNames := make(map[string]bool) + for i, r := range routes { + if r.Port <= 0 { + return fmt.Errorf("route port must be > 0 in component %q of service %q", compName, svcName) + } + if r.Mode != "" && r.Mode != "l4" && r.Mode != "l7" { + return fmt.Errorf("route mode must be \"l4\" or \"l7\" in component %q of service %q", compName, svcName) + } + if len(routes) > 1 && r.Name == "" { + return fmt.Errorf("route name is required when component has multiple routes in component %q of service %q", compName, svcName) + } + + // Use index-based key for unnamed single routes. + key := r.Name + if key == "" { + key = fmt.Sprintf("_route_%d", i) + } + if routeNames[key] { + return fmt.Errorf("duplicate route name %q in component %q of service %q", r.Name, compName, svcName) + } + routeNames[key] = true + } + return nil +} + // ToProto converts a ServiceDef to a proto ServiceSpec. func ToProto(def *ServiceDef) *mcpv1.ServiceSpec { spec := &mcpv1.ServiceSpec{ @@ -142,7 +188,7 @@ func ToProto(def *ServiceDef) *mcpv1.ServiceSpec { } for _, c := range def.Components { - spec.Components = append(spec.Components, &mcpv1.ComponentSpec{ + cs := &mcpv1.ComponentSpec{ Name: c.Name, Image: c.Image, Network: c.Network, @@ -151,7 +197,17 @@ func ToProto(def *ServiceDef) *mcpv1.ServiceSpec { Ports: c.Ports, Volumes: c.Volumes, Cmd: c.Cmd, - }) + Env: c.Env, + } + for _, r := range c.Routes { + cs.Routes = append(cs.Routes, &mcpv1.RouteSpec{ + Name: r.Name, + Port: int32(r.Port), + Mode: r.Mode, + Hostname: r.Hostname, + }) + } + spec.Components = append(spec.Components, cs) } return spec @@ -169,7 +225,7 @@ func FromProto(spec *mcpv1.ServiceSpec, node string) *ServiceDef { } for _, c := range spec.GetComponents() { - def.Components = append(def.Components, ComponentDef{ + cd := ComponentDef{ Name: c.GetName(), Image: c.GetImage(), Network: c.GetNetwork(), @@ -178,7 +234,17 @@ func FromProto(spec *mcpv1.ServiceSpec, node string) *ServiceDef { Ports: c.GetPorts(), Volumes: c.GetVolumes(), Cmd: c.GetCmd(), - }) + Env: c.GetEnv(), + } + for _, r := range c.GetRoutes() { + cd.Routes = append(cd.Routes, RouteDef{ + Name: r.GetName(), + Port: int(r.GetPort()), + Mode: r.GetMode(), + Hostname: r.GetHostname(), + }) + } + def.Components = append(def.Components, cd) } return def diff --git a/internal/servicedef/servicedef_test.go b/internal/servicedef/servicedef_test.go index 11f52f7..bfe3a6b 100644 --- a/internal/servicedef/servicedef_test.go +++ b/internal/servicedef/servicedef_test.go @@ -261,6 +261,203 @@ image = "img:latest" } } +func TestLoadWriteWithRoutes(t *testing.T) { + def := &ServiceDef{ + Name: "myservice", + Node: "rift", + Active: boolPtr(true), + Components: []ComponentDef{ + { + Name: "api", + Image: "img:latest", + Network: "docker_default", + Routes: []RouteDef{ + {Name: "rest", Port: 8443, Mode: "l7", Hostname: "api.example.com"}, + {Name: "grpc", Port: 9443, Mode: "l4"}, + }, + Env: []string{"FOO=bar"}, + }, + }, + } + + dir := t.TempDir() + path := filepath.Join(dir, "myservice.toml") + + if err := Write(path, def); err != nil { + t.Fatalf("write: %v", err) + } + + got, err := Load(path) + if err != nil { + t.Fatalf("load: %v", err) + } + + if len(got.Components[0].Routes) != 2 { + t.Fatalf("routes: got %d, want 2", len(got.Components[0].Routes)) + } + + r := got.Components[0].Routes[0] + if r.Name != "rest" || r.Port != 8443 || r.Mode != "l7" || r.Hostname != "api.example.com" { + t.Fatalf("route[0] mismatch: %+v", r) + } + r2 := got.Components[0].Routes[1] + if r2.Name != "grpc" || r2.Port != 9443 || r2.Mode != "l4" { + t.Fatalf("route[1] mismatch: %+v", r2) + } + + if len(got.Components[0].Env) != 1 || got.Components[0].Env[0] != "FOO=bar" { + t.Fatalf("env mismatch: %v", got.Components[0].Env) + } +} + +func TestRouteValidation(t *testing.T) { + tests := []struct { + name string + def *ServiceDef + wantErr string + }{ + { + name: "route missing port", + def: &ServiceDef{ + Name: "svc", Node: "rift", + Components: []ComponentDef{{ + Name: "api", + Image: "img:v1", + Routes: []RouteDef{{Name: "rest", Port: 0}}, + }}, + }, + wantErr: "route port must be > 0", + }, + { + name: "route invalid mode", + def: &ServiceDef{ + Name: "svc", Node: "rift", + Components: []ComponentDef{{ + Name: "api", + Image: "img:v1", + Routes: []RouteDef{{Port: 8443, Mode: "tcp"}}, + }}, + }, + wantErr: "route mode must be", + }, + { + name: "multi-route missing name", + def: &ServiceDef{ + Name: "svc", Node: "rift", + Components: []ComponentDef{{ + Name: "api", + Image: "img:v1", + Routes: []RouteDef{ + {Name: "rest", Port: 8443}, + {Port: 9443}, + }, + }}, + }, + wantErr: "route name is required when component has multiple routes", + }, + { + name: "duplicate route name", + def: &ServiceDef{ + Name: "svc", Node: "rift", + Components: []ComponentDef{{ + Name: "api", + Image: "img:v1", + Routes: []RouteDef{ + {Name: "rest", Port: 8443}, + {Name: "rest", Port: 9443}, + }, + }}, + }, + wantErr: "duplicate route name", + }, + { + name: "single unnamed route is valid", + def: &ServiceDef{ + Name: "svc", Node: "rift", + Components: []ComponentDef{{ + Name: "api", + Image: "img:v1", + Routes: []RouteDef{{Port: 8443}}, + }}, + }, + wantErr: "", + }, + { + name: "valid l4 mode", + def: &ServiceDef{ + Name: "svc", Node: "rift", + Components: []ComponentDef{{ + Name: "api", + Image: "img:v1", + Routes: []RouteDef{{Port: 8443, Mode: "l4"}}, + }}, + }, + wantErr: "", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := validate(tt.def) + if tt.wantErr == "" { + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + return + } + if err == nil { + t.Fatal("expected validation error") + } + if got := err.Error(); !strings.Contains(got, tt.wantErr) { + t.Fatalf("error %q does not contain %q", got, tt.wantErr) + } + }) + } +} + +func TestProtoConversionWithRoutes(t *testing.T) { + def := &ServiceDef{ + Name: "svc", + Node: "rift", + Active: boolPtr(true), + Components: []ComponentDef{ + { + Name: "api", + Image: "img:v1", + Routes: []RouteDef{ + {Name: "rest", Port: 8443, Mode: "l7", Hostname: "api.example.com"}, + {Name: "grpc", Port: 9443, Mode: "l4"}, + }, + Env: []string{"PORT_REST=12345", "PORT_GRPC=12346"}, + }, + }, + } + + spec := ToProto(def) + if len(spec.Components[0].Routes) != 2 { + t.Fatalf("proto routes: got %d, want 2", len(spec.Components[0].Routes)) + } + r := spec.Components[0].Routes[0] + if r.GetName() != "rest" || r.GetPort() != 8443 || r.GetMode() != "l7" || r.GetHostname() != "api.example.com" { + t.Fatalf("proto route[0] mismatch: %+v", r) + } + if len(spec.Components[0].Env) != 2 { + t.Fatalf("proto env: got %d, want 2", len(spec.Components[0].Env)) + } + + got := FromProto(spec, "rift") + if len(got.Components[0].Routes) != 2 { + t.Fatalf("round-trip routes: got %d, want 2", len(got.Components[0].Routes)) + } + gotR := got.Components[0].Routes[0] + if gotR.Name != "rest" || gotR.Port != 8443 || gotR.Mode != "l7" || gotR.Hostname != "api.example.com" { + t.Fatalf("round-trip route[0] mismatch: %+v", gotR) + } + if len(got.Components[0].Env) != 2 { + t.Fatalf("round-trip env: got %d, want 2", len(got.Components[0].Env)) + } +} + func TestProtoConversion(t *testing.T) { def := sampleDef() diff --git a/proto/mcp/v1/mcp.proto b/proto/mcp/v1/mcp.proto index 93c6de7..8393d95 100644 --- a/proto/mcp/v1/mcp.proto +++ b/proto/mcp/v1/mcp.proto @@ -36,6 +36,13 @@ service McpAgentService { // --- Service lifecycle --- +message RouteSpec { + string name = 1; // route name (used for $PORT_) + int32 port = 2; // external port on mc-proxy + string mode = 3; // "l4" or "l7" + string hostname = 4; // optional public hostname override +} + message ComponentSpec { string name = 1; string image = 2; @@ -45,6 +52,8 @@ message ComponentSpec { repeated string ports = 6; repeated string volumes = 7; repeated string cmd = 8; + repeated RouteSpec routes = 9; + repeated string env = 10; } message ServiceSpec { -- 2.49.1