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/run/run.go

228 lines
5.9 KiB
Go

package run
import (
"bufio"
"crypto/md5"
"fmt"
"io"
"os"
"os/exec"
"os/signal"
"path/filepath"
"strconv"
"strings"
"syscall"
"time"
"github.com/urfave/cli/v2"
"go-micro.dev/v5/cmd"
)
// Color codes for log output
var colors = []string{
"\033[31m", // red
"\033[32m", // green
"\033[33m", // yellow
"\033[34m", // blue
"\033[35m", // magenta
"\033[36m", // cyan
}
func colorFor(idx int) string {
return colors[idx%len(colors)]
}
func Run(c *cli.Context) error {
dir := c.Args().Get(0)
var tmpDir string
if len(dir) == 0 {
dir = "."
} else if strings.HasPrefix(dir, "github.com/") || strings.HasPrefix(dir, "https://github.com/") {
// Handle git URLs
repo := dir
if strings.HasPrefix(repo, "https://") {
repo = strings.TrimPrefix(repo, "https://")
}
// Clone to a temp directory
tmp, err := os.MkdirTemp("", "micro-run-")
if err != nil {
return fmt.Errorf("failed to create temp dir: %w", err)
}
tmpDir = tmp
cloneURL := repo
if !strings.HasPrefix(cloneURL, "https://") {
cloneURL = "https://" + repo
}
// Run git clone
cmd := exec.Command("git", "clone", cloneURL, tmpDir)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
return fmt.Errorf("failed to clone repo %s: %w", cloneURL, err)
}
dir = tmpDir
}
homeDir, err := os.UserHomeDir()
if err != nil {
return fmt.Errorf("failed to get home dir: %w", err)
}
logsDir := filepath.Join(homeDir, "micro", "logs")
if err := os.MkdirAll(logsDir, 0755); err != nil {
return fmt.Errorf("failed to create logs dir: %w", err)
}
runDir := filepath.Join(homeDir, "micro", "run")
if err := os.MkdirAll(runDir, 0755); err != nil {
return fmt.Errorf("failed to create run dir: %w", err)
}
binDir := filepath.Join(homeDir, "micro", "bin")
if err := os.MkdirAll(binDir, 0755); err != nil {
return fmt.Errorf("failed to create bin dir: %w", err)
}
// Always run all services (find all main.go)
var mainFiles []string
err = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if info.IsDir() {
return nil
}
if info.Name() == "main.go" {
mainFiles = append(mainFiles, path)
}
return nil
})
if err != nil {
return fmt.Errorf("error walking the path: %w", err)
}
if len(mainFiles) == 0 {
return fmt.Errorf("no main.go files found in %s", dir)
}
var procs []*exec.Cmd
var pidFiles []string
for i, mainFile := range mainFiles {
serviceDir := filepath.Dir(mainFile)
var serviceName string
absServiceDir, _ := filepath.Abs(serviceDir)
// Determine service name: if absServiceDir matches the provided dir (which may be "."), use cwd
if absServiceDir == dir {
cwd, _ := os.Getwd()
serviceName = filepath.Base(cwd)
} else {
serviceName = filepath.Base(serviceDir)
}
serviceNameForPid := serviceName + "-" + fmt.Sprintf("%x", md5.Sum([]byte(absServiceDir)))[:8]
logFilePath := filepath.Join(logsDir, serviceNameForPid+".log")
binPath := filepath.Join(binDir, serviceNameForPid)
pidFilePath := filepath.Join(runDir, serviceNameForPid+".pid")
// Check if pid file exists and process is running
if pidBytes, err := os.ReadFile(pidFilePath); err == nil {
lines := strings.Split(string(pidBytes), "\n")
if len(lines) > 0 && len(lines[0]) > 0 {
pid := lines[0]
if _, err := os.FindProcess(parsePid(pid)); err == nil {
if processRunning(pid) {
fmt.Fprintf(os.Stderr, "Service %s already running (pid %s)\n", serviceNameForPid, pid)
continue
}
}
}
}
logFile, err := os.OpenFile(logFilePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to open log file for %s: %v\n", serviceName, err)
continue
}
buildCmd := exec.Command("go", "build", "-o", binPath, ".")
buildCmd.Dir = serviceDir
buildOut, buildErr := buildCmd.CombinedOutput()
if buildErr != nil {
logFile.WriteString(string(buildOut))
logFile.Close()
fmt.Fprintf(os.Stderr, "failed to build %s: %v\n", serviceName, buildErr)
continue
}
cmd := exec.Command(binPath)
cmd.Dir = serviceDir
pr, pw := io.Pipe()
cmd.Stdout = pw
cmd.Stderr = pw
color := colorFor(i)
go func(name string, color string, pr *io.PipeReader, logFile *os.File) {
defer logFile.Close()
scanner := bufio.NewScanner(pr)
for scanner.Scan() {
line := scanner.Text()
// Write to terminal with color and service name
fmt.Printf("%s[%s]\033[0m %s\n", color, name, line)
// Write to log file with service name prefix
logFile.WriteString("[" + name + "] " + line + "\n")
}
}(serviceName, color, pr, logFile)
if err := cmd.Start(); err != nil {
fmt.Fprintf(os.Stderr, "failed to start service %s: %v\n", serviceName, err)
pw.Close()
continue
}
procs = append(procs, cmd)
pidFiles = append(pidFiles, pidFilePath)
os.WriteFile(pidFilePath, []byte(fmt.Sprintf("%d\n%s\n%s\n%s\n", cmd.Process.Pid, absServiceDir, serviceName, time.Now().Format(time.RFC3339))), 0644)
}
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 _, proc := range procs {
_ = proc.Wait()
}
return nil
}
// Add helpers for process check
func parsePid(pidStr string) int {
pid, _ := strconv.Atoi(pidStr)
return pid
}
func processRunning(pidStr string) bool {
pid := parsePid(pidStr)
if pid <= 0 {
return false
}
proc, err := os.FindProcess(pid)
if err != nil {
return false
}
// On Unix, sending signal 0 checks if process exists
return proc.Signal(syscall.Signal(0)) == nil
}
func init() {
cmd.Register(&cli.Command{
Name: "run",
Usage: "Run all services in a directory",
Action: Run,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "address",
Aliases: []string{"a"},
Usage: "Address to bind the micro web UI (default :8080)",
Value: ":8080",
},
},
})
}