1
0
mirror of https://github.com/go-micro/go-micro.git synced 2025-11-23 21:44:41 +02:00
Files
go-micro/cmd/micro/cli/cli.go

370 lines
8.4 KiB
Go

package microcli
import (
"bufio"
"context"
"encoding/json"
"fmt"
"os"
"os/exec"
"os/signal"
"path/filepath"
"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/genai"
"go-micro.dev/v5/registry"
"go-micro.dev/v5/cmd/micro/cli/new"
"go-micro.dev/v5/cmd/micro/cli/util"
)
var (
// version is set by the release action
// this is the default for local builds
version = "5.0.0-dev"
)
func genProtoHandler(c *cli.Context) error {
cmd := exec.Command("find", ".", "-name", "*.proto", "-exec", "protoc", "--proto_path=.", "--micro_out=.", "--go_out=.", `{}`, `;`)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
}
func genTextHandler(c *cli.Context) error {
prompt := c.String("prompt")
if len(prompt) == 0 {
return nil
}
gen := genai.DefaultGenAI
if gen.String() == "noop" {
return nil
}
res, err := gen.Generate(prompt)
if err != nil {
return err
}
fmt.Println(res.Text)
return nil
}
func lastNonEmptyLine(s string) string {
lines := strings.Split(s, "\n")
for i := len(lines) - 1; i >= 0; i-- {
if strings.TrimSpace(lines[i]) != "" {
return lines[i]
}
}
return ""
}
func lastLogLine(path string) string {
f, err := os.Open(path)
if err != nil {
return ""
}
defer f.Close()
var last string
scan := bufio.NewScanner(f)
for scan.Scan() {
if strings.TrimSpace(scan.Text()) != "" {
last = scan.Text()
}
}
return last
}
func waitAndCleanup(procs []*exec.Cmd, pidFiles []string) {
ch := make(chan os.Signal, 1)
signal.Notify(ch, os.Interrupt)
go func() {
<-ch
for _, proc := range procs {
if proc.Process != nil {
_ = proc.Process.Kill()
}
}
for _, pf := range pidFiles {
_ = os.Remove(pf)
}
os.Exit(1)
}()
for i, proc := range procs {
_ = proc.Wait()
if proc.Process != nil {
_ = os.Remove(pidFiles[i])
}
}
}
func init() {
cmd.Register([]*cli.Command{
{
Name: "new",
Usage: "Create a new service",
Action: new.Run,
},
{
Name: "gen",
Usage: "Generate various things",
Subcommands: []*cli.Command{
{
Name: "text",
Usage: "Generate text via an LLM",
Action: genTextHandler,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "prompt",
Aliases: []string{"p"},
Usage: "The prompt to generate text from",
},
},
},
{
Name: "proto",
Usage: "Generate proto requires protoc and protoc-gen-micro",
Action: genProtoHandler,
},
},
},
{
Name: "services",
Usage: "List available services",
Action: func(ctx *cli.Context) error {
services, err := registry.ListServices()
if err != nil {
return err
}
for _, service := range services {
fmt.Println(service.Name)
}
return nil
},
},
{
Name: "call",
Usage: "Call a service",
Action: func(ctx *cli.Context) error {
args := ctx.Args()
if args.Len() < 2 {
return fmt.Errorf("Usage: [service] [endpoint] [request]")
}
service := args.Get(0)
endpoint := args.Get(1)
request := `{}`
if args.Len() == 3 {
request = args.Get(2)
}
req := client.NewRequest(service, endpoint, &bytes.Frame{Data: []byte(request)})
var rsp bytes.Frame
err := client.Call(context.TODO(), req, &rsp)
if err != nil {
return err
}
fmt.Print(string(rsp.Data))
return nil
},
},
{
Name: "describe",
Usage: "Describe a service",
Action: func(ctx *cli.Context) error {
args := ctx.Args()
if args.Len() != 1 {
return fmt.Errorf("Usage: [service]")
}
service := args.Get(0)
services, err := registry.GetService(service)
if err != nil {
return err
}
if len(services) == 0 {
return nil
}
b, _ := json.MarshalIndent(services[0], "", " ")
fmt.Println(string(b))
return nil
},
},
{
Name: "status",
Usage: "Check status of running services",
Action: func(ctx *cli.Context) error {
homeDir, err := os.UserHomeDir()
if err != nil {
return fmt.Errorf("failed to get home dir: %w", err)
}
runDir := filepath.Join(homeDir, "micro", "run")
files, err := os.ReadDir(runDir)
if err != nil {
return fmt.Errorf("failed to read run dir: %w", err)
}
fmt.Printf("%-20s %-8s %-8s %s\n", "SERVICE", "PID", "STATUS", "DIRECTORY")
for _, f := range files {
if f.IsDir() || !strings.HasSuffix(f.Name(), ".pid") {
continue
}
service := f.Name()[:len(f.Name())-4]
pidFilePath := filepath.Join(runDir, f.Name())
pidFile, err := os.Open(pidFilePath)
if err != nil {
continue
}
var pid int
var dir string
scanner := bufio.NewScanner(pidFile)
if scanner.Scan() {
fmt.Sscanf(scanner.Text(), "%d", &pid)
}
if scanner.Scan() {
dir = scanner.Text()
}
pidFile.Close()
status := "stopped"
if pid > 0 {
proc, err := os.FindProcess(pid)
if err == nil {
if err := proc.Signal(syscall.Signal(0)); err == nil {
status = "running"
}
}
}
fmt.Printf("%-20s %-8d %-8s %-40s %s\n", service, pid, status, "", dir)
}
return nil
},
},
{
Name: "stop",
Usage: "Stop a running service",
Action: func(ctx *cli.Context) error {
if ctx.Args().Len() != 1 {
return fmt.Errorf("Usage: micro stop [service]")
}
service := ctx.Args().Get(0)
homeDir, err := os.UserHomeDir()
if err != nil {
return fmt.Errorf("failed to get home dir: %w", err)
}
runDir := filepath.Join(homeDir, "micro", "run")
pidFilePath := filepath.Join(runDir, service+".pid")
pidFile, err := os.Open(pidFilePath)
if err != nil {
return fmt.Errorf("no pid file for service %s", service)
}
var pid int
var dir string
scanner := bufio.NewScanner(pidFile)
if scanner.Scan() {
fmt.Sscanf(scanner.Text(), "%d", &pid)
}
if scanner.Scan() {
dir = scanner.Text()
}
pidFile.Close()
if pid <= 0 {
_ = os.Remove(pidFilePath)
return fmt.Errorf("service %s is not running", service)
}
proc, err := os.FindProcess(pid)
if err != nil {
_ = os.Remove(pidFilePath)
return fmt.Errorf("could not find process for %s", service)
}
if err := proc.Signal(syscall.SIGTERM); err != nil {
_ = os.Remove(pidFilePath)
return fmt.Errorf("failed to stop service %s: %v", service, err)
}
_ = os.Remove(pidFilePath)
fmt.Printf("Stopped service %s (pid %d) in directory %s\n", service, pid, dir)
return nil
},
},
{
Name: "logs",
Usage: "Show logs for a service, or list available logs if no service is specified",
Action: func(ctx *cli.Context) error {
homeDir, err := os.UserHomeDir()
if err != nil {
return fmt.Errorf("failed to get home dir: %w", err)
}
logsDir := filepath.Join(homeDir, "micro", "logs")
if ctx.Args().Len() == 0 {
// List available logs
dirEntries, err := os.ReadDir(logsDir)
if err != nil {
return fmt.Errorf("could not list logs directory: %v", err)
}
fmt.Println("Available logs:")
found := false
for _, entry := range dirEntries {
if !entry.IsDir() && strings.HasSuffix(entry.Name(), ".log") {
fmt.Println(" ", strings.TrimSuffix(entry.Name(), ".log"))
found = true
}
}
if !found {
fmt.Println(" (no logs found)")
}
return nil
}
service := ctx.Args().Get(0)
logFilePath := filepath.Join(logsDir, service+".log")
f, err := os.Open(logFilePath)
if err != nil {
return fmt.Errorf("could not open log file for service %s: %v", service, err)
}
defer f.Close()
scan := bufio.NewScanner(f)
for scan.Scan() {
fmt.Println(scan.Text())
}
return scan.Err()
},
},
}...)
cmd.App().Action = func(c *cli.Context) error {
if c.Args().Len() == 0 {
return nil
}
v, err := exec.LookPath("micro-" + c.Args().First())
if err == nil {
ce := exec.Command(v, c.Args().Slice()[1:]...)
ce.Stdout = os.Stdout
ce.Stderr = os.Stderr
return ce.Run()
}
command := c.Args().Get(0)
args := c.Args().Slice()
if srv, err := util.LookupService(command); err != nil {
return util.CliError(err)
} else if srv != nil && util.ShouldRenderHelp(args) {
return cli.Exit(util.FormatServiceUsage(srv, c), 0)
} else if srv != nil {
err := util.CallService(srv, args)
return util.CliError(err)
}
return nil
}
}