Add mcp route command for managing mc-proxy routes
New top-level command with list, add, remove subcommands. Supports -n/--node to target a specific node. Adds AddProxyRoute and RemoveProxyRoute RPCs to the agent. Moves route listing from mcp node routes to mcp route list. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -53,6 +53,7 @@ func main() {
|
||||
root.AddCommand(logsCmd())
|
||||
root.AddCommand(editCmd())
|
||||
root.AddCommand(dnsCmd())
|
||||
root.AddCommand(routeCmd())
|
||||
|
||||
if err := root.Execute(); err != nil {
|
||||
log.Fatal(err)
|
||||
|
||||
@@ -40,62 +40,10 @@ func nodeCmd() *cobra.Command {
|
||||
RunE: runNodeRemove,
|
||||
}
|
||||
|
||||
routes := &cobra.Command{
|
||||
Use: "routes",
|
||||
Short: "List mc-proxy routes on all nodes",
|
||||
RunE: runNodeRoutes,
|
||||
}
|
||||
|
||||
cmd.AddCommand(list, add, remove, routes)
|
||||
cmd.AddCommand(list, add, remove)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runNodeRoutes(_ *cobra.Command, _ []string) error {
|
||||
first := true
|
||||
return forEachNode(func(node config.NodeConfig, client mcpv1.McpAgentServiceClient) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
resp, err := client.ListProxyRoutes(ctx, &mcpv1.ListProxyRoutesRequest{})
|
||||
if err != nil {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "warning: %s: list routes: %v\n", node.Name, err)
|
||||
return nil
|
||||
}
|
||||
|
||||
if !first {
|
||||
fmt.Println()
|
||||
}
|
||||
first = false
|
||||
|
||||
fmt.Printf("NODE: %s\n", node.Name)
|
||||
fmt.Printf("mc-proxy %s\n", resp.GetVersion())
|
||||
if resp.GetStartedAt() != nil {
|
||||
uptime := time.Since(resp.GetStartedAt().AsTime()).Truncate(time.Second)
|
||||
fmt.Printf("uptime: %s\n", uptime)
|
||||
}
|
||||
fmt.Printf("connections: %d\n", resp.GetTotalConnections())
|
||||
fmt.Println()
|
||||
|
||||
for _, ls := range resp.GetListeners() {
|
||||
fmt.Printf(" %s routes=%d active=%d\n",
|
||||
ls.GetAddr(), ls.GetRouteCount(), ls.GetActiveConnections())
|
||||
for _, r := range ls.GetRoutes() {
|
||||
mode := r.GetMode()
|
||||
if mode == "" {
|
||||
mode = "l4"
|
||||
}
|
||||
extra := ""
|
||||
if r.GetBackendTls() {
|
||||
extra = " (re-encrypt)"
|
||||
}
|
||||
fmt.Printf(" %s %s → %s%s\n", mode, r.GetHostname(), r.GetBackend(), extra)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func runNodeList(_ *cobra.Command, _ []string) error {
|
||||
cfg, err := config.LoadCLIConfig(cfgPath)
|
||||
if err != nil {
|
||||
|
||||
212
cmd/mcp/route.go
Normal file
212
cmd/mcp/route.go
Normal file
@@ -0,0 +1,212 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
mcpv1 "git.wntrmute.dev/mc/mcp/gen/mcp/v1"
|
||||
"git.wntrmute.dev/mc/mcp/internal/config"
|
||||
)
|
||||
|
||||
func routeCmd() *cobra.Command {
|
||||
var nodeName string
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "route",
|
||||
Short: "Manage mc-proxy routes",
|
||||
}
|
||||
|
||||
list := &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "List mc-proxy routes",
|
||||
RunE: func(_ *cobra.Command, _ []string) error {
|
||||
return runRouteList(nodeName)
|
||||
},
|
||||
}
|
||||
|
||||
add := &cobra.Command{
|
||||
Use: "add <listener> <hostname> <backend>",
|
||||
Short: "Add a route to mc-proxy",
|
||||
Long: "Add a route. Example: mcp route add -n rift :443 mcq.metacircular.net 100.95.252.120:443",
|
||||
Args: cobra.ExactArgs(3),
|
||||
RunE: func(_ *cobra.Command, args []string) error {
|
||||
return runRouteAdd(nodeName, args)
|
||||
},
|
||||
}
|
||||
add.Flags().String("mode", "l4", "route mode (l4 or l7)")
|
||||
add.Flags().Bool("backend-tls", false, "re-encrypt traffic to backend")
|
||||
|
||||
remove := &cobra.Command{
|
||||
Use: "remove <listener> <hostname>",
|
||||
Short: "Remove a route from mc-proxy",
|
||||
Long: "Remove a route. Example: mcp route remove -n rift :443 mcq.metacircular.net",
|
||||
Args: cobra.ExactArgs(2),
|
||||
RunE: func(_ *cobra.Command, args []string) error {
|
||||
return runRouteRemove(nodeName, args)
|
||||
},
|
||||
}
|
||||
|
||||
cmd.PersistentFlags().StringVarP(&nodeName, "node", "n", "", "target node (required)")
|
||||
|
||||
cmd.AddCommand(list, add, remove)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runRouteList(nodeName string) error {
|
||||
if nodeName == "" {
|
||||
return runRouteListAll()
|
||||
}
|
||||
|
||||
cfg, err := config.LoadCLIConfig(cfgPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("load config: %w", err)
|
||||
}
|
||||
|
||||
address, err := findNodeAddress(cfg, nodeName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
client, conn, err := dialAgent(address, cfg)
|
||||
if err != nil {
|
||||
return fmt.Errorf("dial agent: %w", err)
|
||||
}
|
||||
defer func() { _ = conn.Close() }()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
resp, err := client.ListProxyRoutes(ctx, &mcpv1.ListProxyRoutesRequest{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("list routes: %w", err)
|
||||
}
|
||||
|
||||
printRoutes(nodeName, resp)
|
||||
return nil
|
||||
}
|
||||
|
||||
func runRouteListAll() error {
|
||||
first := true
|
||||
return forEachNode(func(node config.NodeConfig, client mcpv1.McpAgentServiceClient) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
resp, err := client.ListProxyRoutes(ctx, &mcpv1.ListProxyRoutesRequest{})
|
||||
if err != nil {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "warning: %s: list routes: %v\n", node.Name, err)
|
||||
return nil
|
||||
}
|
||||
|
||||
if !first {
|
||||
fmt.Println()
|
||||
}
|
||||
first = false
|
||||
|
||||
printRoutes(node.Name, resp)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func printRoutes(nodeName string, resp *mcpv1.ListProxyRoutesResponse) {
|
||||
fmt.Printf("NODE: %s\n", nodeName)
|
||||
fmt.Printf("mc-proxy %s\n", resp.GetVersion())
|
||||
if resp.GetStartedAt() != nil {
|
||||
uptime := time.Since(resp.GetStartedAt().AsTime()).Truncate(time.Second)
|
||||
fmt.Printf("uptime: %s\n", uptime)
|
||||
}
|
||||
fmt.Printf("connections: %d\n", resp.GetTotalConnections())
|
||||
fmt.Println()
|
||||
|
||||
for _, ls := range resp.GetListeners() {
|
||||
fmt.Printf(" %s routes=%d active=%d\n",
|
||||
ls.GetAddr(), ls.GetRouteCount(), ls.GetActiveConnections())
|
||||
for _, r := range ls.GetRoutes() {
|
||||
mode := r.GetMode()
|
||||
if mode == "" {
|
||||
mode = "l4"
|
||||
}
|
||||
extra := ""
|
||||
if r.GetBackendTls() {
|
||||
extra = " (re-encrypt)"
|
||||
}
|
||||
fmt.Printf(" %s %s → %s%s\n", mode, r.GetHostname(), r.GetBackend(), extra)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func runRouteAdd(nodeName string, args []string) error {
|
||||
if nodeName == "" {
|
||||
return fmt.Errorf("--node is required")
|
||||
}
|
||||
|
||||
cfg, err := config.LoadCLIConfig(cfgPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("load config: %w", err)
|
||||
}
|
||||
|
||||
address, err := findNodeAddress(cfg, nodeName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
client, conn, err := dialAgent(address, cfg)
|
||||
if err != nil {
|
||||
return fmt.Errorf("dial agent: %w", err)
|
||||
}
|
||||
defer func() { _ = conn.Close() }()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
_, err = client.AddProxyRoute(ctx, &mcpv1.AddProxyRouteRequest{
|
||||
ListenerAddr: args[0],
|
||||
Hostname: args[1],
|
||||
Backend: args[2],
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("add route: %w", err)
|
||||
}
|
||||
|
||||
fmt.Printf("Added route: %s → %s on %s (%s)\n", args[1], args[2], args[0], nodeName)
|
||||
return nil
|
||||
}
|
||||
|
||||
func runRouteRemove(nodeName string, args []string) error {
|
||||
if nodeName == "" {
|
||||
return fmt.Errorf("--node is required")
|
||||
}
|
||||
|
||||
cfg, err := config.LoadCLIConfig(cfgPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("load config: %w", err)
|
||||
}
|
||||
|
||||
address, err := findNodeAddress(cfg, nodeName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
client, conn, err := dialAgent(address, cfg)
|
||||
if err != nil {
|
||||
return fmt.Errorf("dial agent: %w", err)
|
||||
}
|
||||
defer func() { _ = conn.Close() }()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
_, err = client.RemoveProxyRoute(ctx, &mcpv1.RemoveProxyRouteRequest{
|
||||
ListenerAddr: args[0],
|
||||
Hostname: args[1],
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("remove route: %w", err)
|
||||
}
|
||||
|
||||
fmt.Printf("Removed route: %s from %s (%s)\n", args[1], args[0], nodeName)
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user