mirror of
https://github.com/go-micro/go-micro.git
synced 2025-11-23 21:44:41 +02:00
move micro cli and protoc-gen-micro to cmd/
This commit is contained in:
369
cmd/micro/cli/cli.go
Normal file
369
cmd/micro/cli/cli.go
Normal file
@@ -0,0 +1,369 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user