diff --git a/cmd/mcp-agent/main.go b/cmd/mcp-agent/main.go index 0a69636..1814687 100644 --- a/cmd/mcp-agent/main.go +++ b/cmd/mcp-agent/main.go @@ -38,7 +38,7 @@ func main() { if err != nil { return fmt.Errorf("load config: %w", err) } - return agent.Run(cfg) + return agent.Run(cfg, version) }, }) diff --git a/cmd/mcp/node.go b/cmd/mcp/node.go index f76f62c..25113d3 100644 --- a/cmd/mcp/node.go +++ b/cmd/mcp/node.go @@ -1,12 +1,15 @@ package main import ( + "context" "fmt" "os" "text/tabwriter" + "time" toml "github.com/pelletier/go-toml/v2" + mcpv1 "git.wntrmute.dev/mc/mcp/gen/mcp/v1" "git.wntrmute.dev/mc/mcp/internal/config" "github.com/spf13/cobra" ) @@ -48,13 +51,35 @@ func runNodeList(_ *cobra.Command, _ []string) error { } w := tabwriter.NewWriter(os.Stdout, 0, 4, 2, ' ', 0) - _, _ = fmt.Fprintln(w, "NAME\tADDRESS") + _, _ = fmt.Fprintln(w, "NAME\tADDRESS\tVERSION") for _, n := range cfg.Nodes { - _, _ = fmt.Fprintf(w, "%s\t%s\n", n.Name, n.Address) + ver := queryAgentVersion(cfg, n.Address) + _, _ = fmt.Fprintf(w, "%s\t%s\t%s\n", n.Name, n.Address, ver) } return w.Flush() } +// queryAgentVersion dials the agent and returns its version, or an error indicator. +func queryAgentVersion(cfg *config.CLIConfig, address string) string { + client, conn, err := dialAgent(address, cfg) + if err != nil { + return "error" + } + defer func() { _ = conn.Close() }() + + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + resp, err := client.NodeStatus(ctx, &mcpv1.NodeStatusRequest{}) + if err != nil { + return "error" + } + if resp.AgentVersion == "" { + return "unknown" + } + return resp.AgentVersion +} + func runNodeAdd(_ *cobra.Command, args []string) error { cfg, err := config.LoadCLIConfig(cfgPath) if err != nil { diff --git a/gen/mcp/v1/mcp.pb.go b/gen/mcp/v1/mcp.pb.go index 45f7a7e..6131208 100644 --- a/gen/mcp/v1/mcp.pb.go +++ b/gen/mcp/v1/mcp.pb.go @@ -1926,6 +1926,7 @@ type NodeStatusResponse struct { MemoryFreeBytes uint64 `protobuf:"varint,9,opt,name=memory_free_bytes,json=memoryFreeBytes,proto3" json:"memory_free_bytes,omitempty"` CpuUsagePercent float64 `protobuf:"fixed64,10,opt,name=cpu_usage_percent,json=cpuUsagePercent,proto3" json:"cpu_usage_percent,omitempty"` UptimeSince *timestamppb.Timestamp `protobuf:"bytes,11,opt,name=uptime_since,json=uptimeSince,proto3" json:"uptime_since,omitempty"` + AgentVersion string `protobuf:"bytes,12,opt,name=agent_version,json=agentVersion,proto3" json:"agent_version,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -2037,6 +2038,13 @@ func (x *NodeStatusResponse) GetUptimeSince() *timestamppb.Timestamp { return nil } +func (x *NodeStatusResponse) GetAgentVersion() string { + if x != nil { + return x.AgentVersion + } + return "" +} + type PurgeRequest struct { state protoimpl.MessageState `protogen:"open.v1"` // Service name (empty = all services). @@ -2474,7 +2482,7 @@ const file_proto_mcp_v1_mcp_proto_rawDesc = "" + "\acontent\x18\x01 \x01(\fR\acontent\x12\x12\n" + "\x04mode\x18\x02 \x01(\rR\x04mode\x12\x14\n" + "\x05error\x18\x03 \x01(\tR\x05error\"\x13\n" + - "\x11NodeStatusRequest\"\xd9\x03\n" + + "\x11NodeStatusRequest\"\xfe\x03\n" + "\x12NodeStatusResponse\x12\x1b\n" + "\tnode_name\x18\x01 \x01(\tR\bnodeName\x12\x18\n" + "\aruntime\x18\x02 \x01(\tR\aruntime\x12'\n" + @@ -2487,7 +2495,8 @@ const file_proto_mcp_v1_mcp_proto_rawDesc = "" + "\x11memory_free_bytes\x18\t \x01(\x04R\x0fmemoryFreeBytes\x12*\n" + "\x11cpu_usage_percent\x18\n" + " \x01(\x01R\x0fcpuUsagePercent\x12=\n" + - "\fuptime_since\x18\v \x01(\v2\x1a.google.protobuf.TimestampR\vuptimeSince\"\x8e\x01\n" + + "\fuptime_since\x18\v \x01(\v2\x1a.google.protobuf.TimestampR\vuptimeSince\x12#\n" + + "\ragent_version\x18\f \x01(\tR\fagentVersion\"\x8e\x01\n" + "\fPurgeRequest\x12\x18\n" + "\aservice\x18\x01 \x01(\tR\aservice\x12\x1c\n" + "\tcomponent\x18\x02 \x01(\tR\tcomponent\x12\x17\n" + diff --git a/internal/agent/agent.go b/internal/agent/agent.go index 72c2307..0cd7431 100644 --- a/internal/agent/agent.go +++ b/internal/agent/agent.go @@ -35,11 +35,12 @@ type Agent struct { Proxy *ProxyRouter Certs *CertProvisioner DNS *DNSRegistrar + Version string } // Run starts the agent: opens the database, sets up the gRPC server with // TLS and auth, and blocks until SIGINT/SIGTERM. -func Run(cfg *config.AgentConfig) error { +func Run(cfg *config.AgentConfig, version string) error { logger := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{ Level: parseLogLevel(cfg.Log.Level), })) @@ -79,6 +80,7 @@ func Run(cfg *config.AgentConfig) error { Proxy: proxy, Certs: certs, DNS: dns, + Version: version, } tlsCert, err := tls.LoadX509KeyPair(cfg.Server.TLSCert, cfg.Server.TLSKey) diff --git a/internal/agent/nodestatus.go b/internal/agent/nodestatus.go index 0e25fd0..4e72b9b 100644 --- a/internal/agent/nodestatus.go +++ b/internal/agent/nodestatus.go @@ -31,6 +31,7 @@ func (a *Agent) NodeStatus(ctx context.Context, _ *mcpv1.NodeStatusRequest) (*mc Runtime: a.Config.Agent.ContainerRuntime, ServiceCount: uint32(len(services)), //nolint:gosec // bounded ComponentCount: componentCount, + AgentVersion: a.Version, } // Runtime version. diff --git a/proto/mcp/v1/mcp.proto b/proto/mcp/v1/mcp.proto index 92e730b..2f073ae 100644 --- a/proto/mcp/v1/mcp.proto +++ b/proto/mcp/v1/mcp.proto @@ -257,6 +257,7 @@ message NodeStatusResponse { uint64 memory_free_bytes = 9; double cpu_usage_percent = 10; google.protobuf.Timestamp uptime_since = 11; + string agent_version = 12; } // --- Purge ---