Add edge CLI scaffolding for Phase 2 testing
Temporary CLI commands for testing edge routing RPCs directly (before the master exists): mcp edge list -n svc mcp edge setup <hostname> -n svc --backend-hostname ... --backend-port ... mcp edge remove <hostname> -n svc Verified end-to-end on svc: setup provisions route in mc-proxy and persists in agent registry, remove cleans up both, list shows routes with cert metadata. Finding: MCNS registers LAN IPs for .svc.mcp. hostnames, not Tailnet IPs. The v2 master needs to register Tailnet IPs in deploy flow step 3. These commands will be removed or replaced when the master is built (Phase 3). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
180
cmd/mcp/edge.go
Normal file
180
cmd/mcp/edge.go
Normal file
@@ -0,0 +1,180 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
mcpv1 "git.wntrmute.dev/mc/mcp/gen/mcp/v1"
|
||||
"git.wntrmute.dev/mc/mcp/internal/config"
|
||||
)
|
||||
|
||||
func edgeCmd() *cobra.Command {
|
||||
var nodeName string
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "edge",
|
||||
Short: "Manage edge routes (scaffolding — will be replaced by master)",
|
||||
}
|
||||
|
||||
list := &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "List edge routes on a node",
|
||||
RunE: func(_ *cobra.Command, _ []string) error {
|
||||
if nodeName == "" {
|
||||
return fmt.Errorf("--node is required")
|
||||
}
|
||||
return runEdgeList(nodeName)
|
||||
},
|
||||
}
|
||||
|
||||
var (
|
||||
backendHostname string
|
||||
backendPort int
|
||||
)
|
||||
|
||||
setup := &cobra.Command{
|
||||
Use: "setup <hostname>",
|
||||
Short: "Set up an edge route (provisions cert, registers mc-proxy route)",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(_ *cobra.Command, args []string) error {
|
||||
if nodeName == "" {
|
||||
return fmt.Errorf("--node is required")
|
||||
}
|
||||
if backendHostname == "" {
|
||||
return fmt.Errorf("--backend-hostname is required")
|
||||
}
|
||||
if backendPort == 0 {
|
||||
return fmt.Errorf("--backend-port is required")
|
||||
}
|
||||
return runEdgeSetup(nodeName, args[0], backendHostname, backendPort)
|
||||
},
|
||||
}
|
||||
setup.Flags().StringVar(&backendHostname, "backend-hostname", "", "internal .svc.mcp hostname")
|
||||
setup.Flags().IntVar(&backendPort, "backend-port", 0, "port on worker's mc-proxy")
|
||||
|
||||
remove := &cobra.Command{
|
||||
Use: "remove <hostname>",
|
||||
Short: "Remove an edge route",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(_ *cobra.Command, args []string) error {
|
||||
if nodeName == "" {
|
||||
return fmt.Errorf("--node is required")
|
||||
}
|
||||
return runEdgeRemove(nodeName, args[0])
|
||||
},
|
||||
}
|
||||
|
||||
cmd.PersistentFlags().StringVarP(&nodeName, "node", "n", "", "target node (required)")
|
||||
cmd.AddCommand(list, setup, remove)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runEdgeList(nodeName string) error {
|
||||
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.ListEdgeRoutes(ctx, &mcpv1.ListEdgeRoutesRequest{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("list edge routes: %w", err)
|
||||
}
|
||||
|
||||
if len(resp.GetRoutes()) == 0 {
|
||||
fmt.Printf("No edge routes on %s\n", nodeName)
|
||||
return nil
|
||||
}
|
||||
|
||||
fmt.Printf("Edge routes on %s:\n", nodeName)
|
||||
for _, r := range resp.GetRoutes() {
|
||||
expires := r.GetCertExpires()
|
||||
if expires == "" {
|
||||
expires = "unknown"
|
||||
}
|
||||
fmt.Printf(" %s → %s:%d cert_expires=%s\n",
|
||||
r.GetHostname(), r.GetBackendHostname(), r.GetBackendPort(), expires)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func runEdgeSetup(nodeName, hostname, backendHostname string, backendPort int) error {
|
||||
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(), 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
_, err = client.SetupEdgeRoute(ctx, &mcpv1.SetupEdgeRouteRequest{
|
||||
Hostname: hostname,
|
||||
BackendHostname: backendHostname,
|
||||
BackendPort: int32(backendPort), //nolint:gosec // port is a small positive integer
|
||||
BackendTls: true,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("setup edge route: %w", err)
|
||||
}
|
||||
|
||||
fmt.Printf("edge route established: %s → %s:%d on %s\n", hostname, backendHostname, backendPort, nodeName)
|
||||
return nil
|
||||
}
|
||||
|
||||
func runEdgeRemove(nodeName, hostname string) error {
|
||||
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(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
_, err = client.RemoveEdgeRoute(ctx, &mcpv1.RemoveEdgeRouteRequest{
|
||||
Hostname: hostname,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("remove edge route: %w", err)
|
||||
}
|
||||
|
||||
fmt.Printf("edge route removed: %s on %s\n", hostname, nodeName)
|
||||
return nil
|
||||
}
|
||||
@@ -54,6 +54,7 @@ func main() {
|
||||
root.AddCommand(editCmd())
|
||||
root.AddCommand(dnsCmd())
|
||||
root.AddCommand(routeCmd())
|
||||
root.AddCommand(edgeCmd())
|
||||
|
||||
if err := root.Execute(); err != nil {
|
||||
log.Fatal(err)
|
||||
|
||||
Reference in New Issue
Block a user