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>
213 lines
5.0 KiB
Go
213 lines
5.0 KiB
Go
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
|
|
}
|