go-micro.dev/v5@v5.12.0/cmd/micro/run/run.go (about) 1 package run 2 3 import ( 4 "bufio" 5 "crypto/md5" 6 "fmt" 7 "io" 8 "os" 9 "os/exec" 10 "os/signal" 11 "path/filepath" 12 "strconv" 13 "strings" 14 "syscall" 15 "time" 16 17 "github.com/urfave/cli/v2" 18 "go-micro.dev/v5/cmd" 19 ) 20 21 // Color codes for log output 22 var colors = []string{ 23 "\033[31m", // red 24 "\033[32m", // green 25 "\033[33m", // yellow 26 "\033[34m", // blue 27 "\033[35m", // magenta 28 "\033[36m", // cyan 29 } 30 31 func colorFor(idx int) string { 32 return colors[idx%len(colors)] 33 } 34 35 func Run(c *cli.Context) error { 36 dir := c.Args().Get(0) 37 var tmpDir string 38 if len(dir) == 0 { 39 dir = "." 40 } else if strings.HasPrefix(dir, "github.com/") || strings.HasPrefix(dir, "https://github.com/") { 41 // Handle git URLs 42 repo := dir 43 if strings.HasPrefix(repo, "https://") { 44 repo = strings.TrimPrefix(repo, "https://") 45 } 46 // Clone to a temp directory 47 tmp, err := os.MkdirTemp("", "micro-run-") 48 if err != nil { 49 return fmt.Errorf("failed to create temp dir: %w", err) 50 } 51 tmpDir = tmp 52 cloneURL := repo 53 if !strings.HasPrefix(cloneURL, "https://") { 54 cloneURL = "https://" + repo 55 } 56 // Run git clone 57 cmd := exec.Command("git", "clone", cloneURL, tmpDir) 58 cmd.Stdout = os.Stdout 59 cmd.Stderr = os.Stderr 60 if err := cmd.Run(); err != nil { 61 return fmt.Errorf("failed to clone repo %s: %w", cloneURL, err) 62 } 63 dir = tmpDir 64 } 65 66 homeDir, err := os.UserHomeDir() 67 if err != nil { 68 return fmt.Errorf("failed to get home dir: %w", err) 69 } 70 logsDir := filepath.Join(homeDir, "micro", "logs") 71 if err := os.MkdirAll(logsDir, 0755); err != nil { 72 return fmt.Errorf("failed to create logs dir: %w", err) 73 } 74 runDir := filepath.Join(homeDir, "micro", "run") 75 if err := os.MkdirAll(runDir, 0755); err != nil { 76 return fmt.Errorf("failed to create run dir: %w", err) 77 } 78 binDir := filepath.Join(homeDir, "micro", "bin") 79 if err := os.MkdirAll(binDir, 0755); err != nil { 80 return fmt.Errorf("failed to create bin dir: %w", err) 81 } 82 83 // Always run all services (find all main.go) 84 var mainFiles []string 85 err = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { 86 if err != nil { 87 return err 88 } 89 if info.IsDir() { 90 return nil 91 } 92 if info.Name() == "main.go" { 93 mainFiles = append(mainFiles, path) 94 } 95 return nil 96 }) 97 if err != nil { 98 return fmt.Errorf("error walking the path: %w", err) 99 } 100 if len(mainFiles) == 0 { 101 return fmt.Errorf("no main.go files found in %s", dir) 102 } 103 var procs []*exec.Cmd 104 var pidFiles []string 105 for i, mainFile := range mainFiles { 106 serviceDir := filepath.Dir(mainFile) 107 var serviceName string 108 absServiceDir, _ := filepath.Abs(serviceDir) 109 // Determine service name: if absServiceDir matches the provided dir (which may be "."), use cwd 110 if absServiceDir == dir { 111 cwd, _ := os.Getwd() 112 serviceName = filepath.Base(cwd) 113 } else { 114 serviceName = filepath.Base(serviceDir) 115 } 116 serviceNameForPid := serviceName + "-" + fmt.Sprintf("%x", md5.Sum([]byte(absServiceDir)))[:8] 117 logFilePath := filepath.Join(logsDir, serviceNameForPid+".log") 118 binPath := filepath.Join(binDir, serviceNameForPid) 119 pidFilePath := filepath.Join(runDir, serviceNameForPid+".pid") 120 121 // Check if pid file exists and process is running 122 if pidBytes, err := os.ReadFile(pidFilePath); err == nil { 123 lines := strings.Split(string(pidBytes), "\n") 124 if len(lines) > 0 && len(lines[0]) > 0 { 125 pid := lines[0] 126 if _, err := os.FindProcess(parsePid(pid)); err == nil { 127 if processRunning(pid) { 128 fmt.Fprintf(os.Stderr, "Service %s already running (pid %s)\n", serviceNameForPid, pid) 129 continue 130 } 131 } 132 } 133 } 134 135 logFile, err := os.OpenFile(logFilePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) 136 if err != nil { 137 fmt.Fprintf(os.Stderr, "failed to open log file for %s: %v\n", serviceName, err) 138 continue 139 } 140 buildCmd := exec.Command("go", "build", "-o", binPath, ".") 141 buildCmd.Dir = serviceDir 142 buildOut, buildErr := buildCmd.CombinedOutput() 143 if buildErr != nil { 144 logFile.WriteString(string(buildOut)) 145 logFile.Close() 146 fmt.Fprintf(os.Stderr, "failed to build %s: %v\n", serviceName, buildErr) 147 continue 148 } 149 cmd := exec.Command(binPath) 150 cmd.Dir = serviceDir 151 pr, pw := io.Pipe() 152 cmd.Stdout = pw 153 cmd.Stderr = pw 154 color := colorFor(i) 155 go func(name string, color string, pr *io.PipeReader, logFile *os.File) { 156 defer logFile.Close() 157 scanner := bufio.NewScanner(pr) 158 for scanner.Scan() { 159 line := scanner.Text() 160 // Write to terminal with color and service name 161 fmt.Printf("%s[%s]\033[0m %s\n", color, name, line) 162 // Write to log file with service name prefix 163 logFile.WriteString("[" + name + "] " + line + "\n") 164 } 165 }(serviceName, color, pr, logFile) 166 if err := cmd.Start(); err != nil { 167 fmt.Fprintf(os.Stderr, "failed to start service %s: %v\n", serviceName, err) 168 pw.Close() 169 continue 170 } 171 procs = append(procs, cmd) 172 pidFiles = append(pidFiles, pidFilePath) 173 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) 174 } 175 ch := make(chan os.Signal, 1) 176 signal.Notify(ch, os.Interrupt) 177 go func() { 178 <-ch 179 for _, proc := range procs { 180 if proc.Process != nil { 181 _ = proc.Process.Kill() 182 } 183 } 184 for _, pf := range pidFiles { 185 _ = os.Remove(pf) 186 } 187 os.Exit(1) 188 }() 189 for _, proc := range procs { 190 _ = proc.Wait() 191 } 192 return nil 193 } 194 195 // Add helpers for process check 196 func parsePid(pidStr string) int { 197 pid, _ := strconv.Atoi(pidStr) 198 return pid 199 } 200 func processRunning(pidStr string) bool { 201 pid := parsePid(pidStr) 202 if pid <= 0 { 203 return false 204 } 205 proc, err := os.FindProcess(pid) 206 if err != nil { 207 return false 208 } 209 // On Unix, sending signal 0 checks if process exists 210 return proc.Signal(syscall.Signal(0)) == nil 211 } 212 213 func init() { 214 cmd.Register(&cli.Command{ 215 Name: "run", 216 Usage: "Run all services in a directory", 217 Action: Run, 218 Flags: []cli.Flag{ 219 &cli.StringFlag{ 220 Name: "address", 221 Aliases: []string{"a"}, 222 Usage: "Address to bind the micro web UI (default :8080)", 223 Value: ":8080", 224 }, 225 }, 226 }) 227 }