Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 28ca1b9aa2 | |||
| 69079fd4fa | |||
| a09512130d |
@@ -105,21 +105,6 @@ func deploySSH(c *cli.Context, target string, cfg *config.Config) error {
|
||||
|
||||
fmt.Printf("Deploying to %s...\n\n", target)
|
||||
|
||||
// Early validation: Check if the requested service exists before SSH checks
|
||||
filterService := c.String("service")
|
||||
if filterService != "" && cfg != nil {
|
||||
found := false
|
||||
for _, svc := range cfg.Services {
|
||||
if svc.Name == filterService {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found && len(cfg.Services) > 0 {
|
||||
return fmt.Errorf("service '%s' not found in configuration", filterService)
|
||||
}
|
||||
}
|
||||
|
||||
// Step 1: Check SSH connectivity
|
||||
fmt.Print(" Checking SSH connection... ")
|
||||
if err := checkSSH(target); err != nil {
|
||||
@@ -144,23 +129,14 @@ func deploySSH(c *cli.Context, target string, cfg *config.Config) error {
|
||||
return err
|
||||
}
|
||||
for _, svc := range sorted {
|
||||
// If --service flag is provided, only include that service
|
||||
if filterService == "" || svc.Name == filterService {
|
||||
services = append(services, svc.Name)
|
||||
}
|
||||
services = append(services, svc.Name)
|
||||
}
|
||||
} else {
|
||||
// Single service project
|
||||
services = []string{filepath.Base(absDir)}
|
||||
|
||||
// If --service flag was provided for a single-service project, validate it matches
|
||||
if filterService != "" && filterService != services[0] {
|
||||
return fmt.Errorf("service '%s' not found (only '%s' available)", filterService, services[0])
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf(" Building binaries... ")
|
||||
if err := buildBinaries(absDir, cfg, c.Bool("build"), services); err != nil {
|
||||
if err := buildBinaries(absDir, cfg, c.Bool("build")); err != nil {
|
||||
fmt.Println("\u2717")
|
||||
return err
|
||||
}
|
||||
@@ -265,7 +241,7 @@ func checkServerInit(host, remotePath string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func buildBinaries(absDir string, cfg *config.Config, forceBuild bool, servicesToBuild []string) error {
|
||||
func buildBinaries(absDir string, cfg *config.Config, forceBuild bool) error {
|
||||
binDir := filepath.Join(absDir, "bin")
|
||||
|
||||
// Check if we already have binaries and don't need to rebuild
|
||||
@@ -290,19 +266,7 @@ func buildBinaries(absDir string, cfg *config.Config, forceBuild bool, servicesT
|
||||
return err
|
||||
}
|
||||
|
||||
// Create a map for quick lookup of services to build
|
||||
// This provides O(1) lookup time and makes the code more maintainable
|
||||
shouldBuild := make(map[string]bool)
|
||||
for _, svcName := range servicesToBuild {
|
||||
shouldBuild[svcName] = true
|
||||
}
|
||||
|
||||
for _, svc := range sorted {
|
||||
// Only build services in the servicesToBuild list
|
||||
if !shouldBuild[svc.Name] {
|
||||
continue
|
||||
}
|
||||
|
||||
svcDir := filepath.Join(absDir, svc.Path)
|
||||
outPath := filepath.Join(binDir, svc.Name)
|
||||
|
||||
@@ -444,9 +408,6 @@ Before deploying, initialize the server:
|
||||
Then deploy:
|
||||
micro deploy user@server
|
||||
|
||||
Deploy a specific service (multi-service projects):
|
||||
micro deploy user@server --service users
|
||||
|
||||
With a micro.mu config, you can define named targets:
|
||||
deploy prod
|
||||
ssh user@prod.example.com
|
||||
@@ -481,10 +442,6 @@ The deploy process:
|
||||
Name: "build",
|
||||
Usage: "Force rebuild of binaries",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "service",
|
||||
Usage: "Deploy only a specific service (for multi-service projects)",
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
+106
-3
@@ -8,10 +8,13 @@ import (
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
"go-micro.dev/v5/client"
|
||||
"go-micro.dev/v5/cmd"
|
||||
"go-micro.dev/v5/codec/bytes"
|
||||
"go-micro.dev/v5/gateway/mcp"
|
||||
"go-micro.dev/v5/registry"
|
||||
)
|
||||
@@ -247,10 +250,110 @@ func testAction(ctx *cli.Context) error {
|
||||
inputJSON = ctx.Args().Get(1)
|
||||
}
|
||||
|
||||
// Validate input JSON
|
||||
var inputData map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(inputJSON), &inputData); err != nil {
|
||||
return fmt.Errorf("invalid JSON input: %w", err)
|
||||
}
|
||||
|
||||
// Get registry
|
||||
reg := registry.DefaultRegistry
|
||||
if regName := ctx.String("registry"); regName != "" {
|
||||
if regName != "mdns" {
|
||||
return fmt.Errorf("registry %s not yet supported, use mdns", regName)
|
||||
}
|
||||
}
|
||||
|
||||
// Create MCP options
|
||||
opts := mcp.Options{
|
||||
Registry: reg,
|
||||
Context: context.Background(),
|
||||
Logger: log.New(os.Stderr, "", 0),
|
||||
}
|
||||
|
||||
// Parse tool name (format: "service.endpoint" or "service.Handler.Method")
|
||||
parts := parseTool(toolName)
|
||||
if len(parts) < 2 {
|
||||
return fmt.Errorf("invalid tool name format. Expected: service.endpoint or service.Handler.Method")
|
||||
}
|
||||
|
||||
serviceName := parts[0]
|
||||
endpointName := parts[1]
|
||||
|
||||
// If tool name has 3 parts, combine last two for endpoint (e.g., Handler.Method)
|
||||
if len(parts) == 3 {
|
||||
endpointName = parts[1] + "." + parts[2]
|
||||
}
|
||||
|
||||
// Discover the tool from registry
|
||||
services, err := opts.Registry.GetService(serviceName)
|
||||
if err != nil || len(services) == 0 {
|
||||
return fmt.Errorf("service %s not found: %w", serviceName, err)
|
||||
}
|
||||
|
||||
// Find the endpoint
|
||||
var endpoint *registry.Endpoint
|
||||
for _, ep := range services[0].Endpoints {
|
||||
if ep.Name == endpointName {
|
||||
endpoint = ep
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if endpoint == nil {
|
||||
return fmt.Errorf("endpoint %s not found in service %s", endpointName, serviceName)
|
||||
}
|
||||
|
||||
// Display test info
|
||||
fmt.Printf("Testing tool: %s\n", toolName)
|
||||
fmt.Printf("Input: %s\n", inputJSON)
|
||||
fmt.Println("\nResult:")
|
||||
fmt.Println("(Not yet implemented - coming soon)")
|
||||
fmt.Printf("Service: %s\n", serviceName)
|
||||
fmt.Printf("Endpoint: %s\n", endpointName)
|
||||
fmt.Printf("Input: %s\n\n", inputJSON)
|
||||
|
||||
// Convert input to JSON bytes for RPC call
|
||||
inputBytes, err := json.Marshal(inputData)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal input: %w", err)
|
||||
}
|
||||
|
||||
// Make RPC call using bytes codec
|
||||
c := opts.Client
|
||||
if c == nil {
|
||||
c = client.DefaultClient
|
||||
}
|
||||
|
||||
// Create request with bytes frame
|
||||
req := c.NewRequest(serviceName, endpointName, &bytes.Frame{Data: inputBytes})
|
||||
|
||||
// Make the call
|
||||
var rsp bytes.Frame
|
||||
if err := c.Call(opts.Context, req, &rsp); err != nil {
|
||||
fmt.Printf("❌ Call failed: %v\n", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Parse and display response
|
||||
fmt.Println("✅ Call successful!")
|
||||
fmt.Println("\nResponse:")
|
||||
|
||||
// Try to pretty-print JSON response
|
||||
var result interface{}
|
||||
if err := json.Unmarshal(rsp.Data, &result); err == nil {
|
||||
prettyJSON, err := json.MarshalIndent(result, "", " ")
|
||||
if err == nil {
|
||||
fmt.Println(string(prettyJSON))
|
||||
} else {
|
||||
fmt.Println(string(rsp.Data))
|
||||
}
|
||||
} else {
|
||||
// Not JSON, print raw
|
||||
fmt.Println(string(rsp.Data))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// parseTool splits a tool name into service and endpoint parts
|
||||
func parseTool(toolName string) []string {
|
||||
return strings.Split(toolName, ".")
|
||||
}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
package mcp
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParseTool(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
toolName string
|
||||
want []string
|
||||
}{
|
||||
{
|
||||
name: "simple two-part tool",
|
||||
toolName: "service.endpoint",
|
||||
want: []string{"service", "endpoint"},
|
||||
},
|
||||
{
|
||||
name: "three-part tool (service.Handler.Method)",
|
||||
toolName: "greeter.Greeter.Hello",
|
||||
want: []string{"greeter", "Greeter", "Hello"},
|
||||
},
|
||||
{
|
||||
name: "single part (invalid)",
|
||||
toolName: "service",
|
||||
want: []string{"service"},
|
||||
},
|
||||
{
|
||||
name: "four-part tool",
|
||||
toolName: "users.Users.Get.All",
|
||||
want: []string{"users", "Users", "Get", "All"},
|
||||
},
|
||||
{
|
||||
name: "empty string",
|
||||
toolName: "",
|
||||
want: []string{""},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := parseTool(tt.toolName)
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("parseTool(%q) = %v, want %v", tt.toolName, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user