go-micro.dev/v5@v5.12.0/cmd/micro/cli/cli.go (about) 1 package microcli 2 3 import ( 4 "bufio" 5 "context" 6 "encoding/json" 7 "fmt" 8 "os" 9 "os/exec" 10 "os/signal" 11 "path/filepath" 12 "strings" 13 "syscall" 14 15 "github.com/urfave/cli/v2" 16 "go-micro.dev/v5/client" 17 "go-micro.dev/v5/cmd" 18 "go-micro.dev/v5/codec/bytes" 19 "go-micro.dev/v5/genai" 20 "go-micro.dev/v5/registry" 21 22 "go-micro.dev/v5/cmd/micro/cli/new" 23 "go-micro.dev/v5/cmd/micro/cli/util" 24 ) 25 26 var ( 27 // version is set by the release action 28 // this is the default for local builds 29 version = "5.0.0-dev" 30 ) 31 32 func genProtoHandler(c *cli.Context) error { 33 cmd := exec.Command("find", ".", "-name", "*.proto", "-exec", "protoc", "--proto_path=.", "--micro_out=.", "--go_out=.", `{}`, `;`) 34 cmd.Stdout = os.Stdout 35 cmd.Stderr = os.Stderr 36 return cmd.Run() 37 } 38 39 func genTextHandler(c *cli.Context) error { 40 prompt := c.String("prompt") 41 if len(prompt) == 0 { 42 return nil 43 } 44 45 gen := genai.DefaultGenAI 46 if gen.String() == "noop" { 47 return nil 48 } 49 50 res, err := gen.Generate(prompt) 51 if err != nil { 52 return err 53 } 54 55 fmt.Println(res.Text) 56 return nil 57 } 58 59 func lastNonEmptyLine(s string) string { 60 lines := strings.Split(s, "\n") 61 for i := len(lines) - 1; i >= 0; i-- { 62 if strings.TrimSpace(lines[i]) != "" { 63 return lines[i] 64 } 65 } 66 return "" 67 } 68 69 func lastLogLine(path string) string { 70 f, err := os.Open(path) 71 if err != nil { 72 return "" 73 } 74 defer f.Close() 75 var last string 76 scan := bufio.NewScanner(f) 77 for scan.Scan() { 78 if strings.TrimSpace(scan.Text()) != "" { 79 last = scan.Text() 80 } 81 } 82 return last 83 } 84 85 func waitAndCleanup(procs []*exec.Cmd, pidFiles []string) { 86 ch := make(chan os.Signal, 1) 87 signal.Notify(ch, os.Interrupt) 88 go func() { 89 <-ch 90 for _, proc := range procs { 91 if proc.Process != nil { 92 _ = proc.Process.Kill() 93 } 94 } 95 for _, pf := range pidFiles { 96 _ = os.Remove(pf) 97 } 98 os.Exit(1) 99 }() 100 for i, proc := range procs { 101 _ = proc.Wait() 102 if proc.Process != nil { 103 _ = os.Remove(pidFiles[i]) 104 } 105 } 106 } 107 108 func init() { 109 cmd.Register([]*cli.Command{ 110 { 111 Name: "new", 112 Usage: "Create a new service", 113 Action: new.Run, 114 }, 115 { 116 Name: "gen", 117 Usage: "Generate various things", 118 Subcommands: []*cli.Command{ 119 { 120 Name: "text", 121 Usage: "Generate text via an LLM", 122 Action: genTextHandler, 123 Flags: []cli.Flag{ 124 &cli.StringFlag{ 125 Name: "prompt", 126 Aliases: []string{"p"}, 127 Usage: "The prompt to generate text from", 128 }, 129 }, 130 }, 131 { 132 Name: "proto", 133 Usage: "Generate proto requires protoc and protoc-gen-micro", 134 Action: genProtoHandler, 135 }, 136 }, 137 }, 138 { 139 Name: "services", 140 Usage: "List available services", 141 Action: func(ctx *cli.Context) error { 142 services, err := registry.ListServices() 143 if err != nil { 144 return err 145 } 146 for _, service := range services { 147 fmt.Println(service.Name) 148 } 149 return nil 150 }, 151 }, 152 { 153 Name: "call", 154 Usage: "Call a service", 155 Action: func(ctx *cli.Context) error { 156 args := ctx.Args() 157 158 if args.Len() < 2 { 159 return fmt.Errorf("Usage: [service] [endpoint] [request]") 160 } 161 162 service := args.Get(0) 163 endpoint := args.Get(1) 164 request := `{}` 165 166 if args.Len() == 3 { 167 request = args.Get(2) 168 } 169 170 req := client.NewRequest(service, endpoint, &bytes.Frame{Data: []byte(request)}) 171 var rsp bytes.Frame 172 err := client.Call(context.TODO(), req, &rsp) 173 if err != nil { 174 return err 175 } 176 177 fmt.Print(string(rsp.Data)) 178 return nil 179 }, 180 }, 181 { 182 Name: "describe", 183 Usage: "Describe a service", 184 Action: func(ctx *cli.Context) error { 185 args := ctx.Args() 186 187 if args.Len() != 1 { 188 return fmt.Errorf("Usage: [service]") 189 } 190 191 service := args.Get(0) 192 services, err := registry.GetService(service) 193 if err != nil { 194 return err 195 } 196 if len(services) == 0 { 197 return nil 198 } 199 b, _ := json.MarshalIndent(services[0], "", " ") 200 fmt.Println(string(b)) 201 return nil 202 }, 203 }, 204 { 205 Name: "status", 206 Usage: "Check status of running services", 207 Action: func(ctx *cli.Context) error { 208 homeDir, err := os.UserHomeDir() 209 if err != nil { 210 return fmt.Errorf("failed to get home dir: %w", err) 211 } 212 runDir := filepath.Join(homeDir, "micro", "run") 213 files, err := os.ReadDir(runDir) 214 if err != nil { 215 return fmt.Errorf("failed to read run dir: %w", err) 216 } 217 fmt.Printf("%-20s %-8s %-8s %s\n", "SERVICE", "PID", "STATUS", "DIRECTORY") 218 for _, f := range files { 219 if f.IsDir() || !strings.HasSuffix(f.Name(), ".pid") { 220 continue 221 } 222 service := f.Name()[:len(f.Name())-4] 223 pidFilePath := filepath.Join(runDir, f.Name()) 224 pidFile, err := os.Open(pidFilePath) 225 if err != nil { 226 continue 227 } 228 var pid int 229 var dir string 230 scanner := bufio.NewScanner(pidFile) 231 if scanner.Scan() { 232 fmt.Sscanf(scanner.Text(), "%d", &pid) 233 } 234 if scanner.Scan() { 235 dir = scanner.Text() 236 } 237 pidFile.Close() 238 status := "stopped" 239 if pid > 0 { 240 proc, err := os.FindProcess(pid) 241 if err == nil { 242 if err := proc.Signal(syscall.Signal(0)); err == nil { 243 status = "running" 244 } 245 } 246 } 247 fmt.Printf("%-20s %-8d %-8s %-40s %s\n", service, pid, status, "", dir) 248 } 249 return nil 250 }, 251 }, 252 { 253 Name: "stop", 254 Usage: "Stop a running service", 255 Action: func(ctx *cli.Context) error { 256 if ctx.Args().Len() != 1 { 257 return fmt.Errorf("Usage: micro stop [service]") 258 } 259 service := ctx.Args().Get(0) 260 homeDir, err := os.UserHomeDir() 261 if err != nil { 262 return fmt.Errorf("failed to get home dir: %w", err) 263 } 264 runDir := filepath.Join(homeDir, "micro", "run") 265 pidFilePath := filepath.Join(runDir, service+".pid") 266 pidFile, err := os.Open(pidFilePath) 267 if err != nil { 268 return fmt.Errorf("no pid file for service %s", service) 269 } 270 var pid int 271 var dir string 272 scanner := bufio.NewScanner(pidFile) 273 if scanner.Scan() { 274 fmt.Sscanf(scanner.Text(), "%d", &pid) 275 } 276 if scanner.Scan() { 277 dir = scanner.Text() 278 } 279 pidFile.Close() 280 if pid <= 0 { 281 _ = os.Remove(pidFilePath) 282 return fmt.Errorf("service %s is not running", service) 283 } 284 proc, err := os.FindProcess(pid) 285 if err != nil { 286 _ = os.Remove(pidFilePath) 287 return fmt.Errorf("could not find process for %s", service) 288 } 289 if err := proc.Signal(syscall.SIGTERM); err != nil { 290 _ = os.Remove(pidFilePath) 291 return fmt.Errorf("failed to stop service %s: %v", service, err) 292 } 293 _ = os.Remove(pidFilePath) 294 fmt.Printf("Stopped service %s (pid %d) in directory %s\n", service, pid, dir) 295 return nil 296 }, 297 }, 298 { 299 Name: "logs", 300 Usage: "Show logs for a service, or list available logs if no service is specified", 301 Action: func(ctx *cli.Context) error { 302 homeDir, err := os.UserHomeDir() 303 if err != nil { 304 return fmt.Errorf("failed to get home dir: %w", err) 305 } 306 logsDir := filepath.Join(homeDir, "micro", "logs") 307 if ctx.Args().Len() == 0 { 308 // List available logs 309 dirEntries, err := os.ReadDir(logsDir) 310 if err != nil { 311 return fmt.Errorf("could not list logs directory: %v", err) 312 } 313 fmt.Println("Available logs:") 314 found := false 315 for _, entry := range dirEntries { 316 if !entry.IsDir() && strings.HasSuffix(entry.Name(), ".log") { 317 fmt.Println(" ", strings.TrimSuffix(entry.Name(), ".log")) 318 found = true 319 } 320 } 321 if !found { 322 fmt.Println(" (no logs found)") 323 } 324 return nil 325 } 326 service := ctx.Args().Get(0) 327 logFilePath := filepath.Join(logsDir, service+".log") 328 f, err := os.Open(logFilePath) 329 if err != nil { 330 return fmt.Errorf("could not open log file for service %s: %v", service, err) 331 } 332 defer f.Close() 333 scan := bufio.NewScanner(f) 334 for scan.Scan() { 335 fmt.Println(scan.Text()) 336 } 337 return scan.Err() 338 }, 339 }, 340 }...) 341 342 cmd.App().Action = func(c *cli.Context) error { 343 if c.Args().Len() == 0 { 344 return nil 345 } 346 347 v, err := exec.LookPath("micro-" + c.Args().First()) 348 if err == nil { 349 ce := exec.Command(v, c.Args().Slice()[1:]...) 350 ce.Stdout = os.Stdout 351 ce.Stderr = os.Stderr 352 return ce.Run() 353 } 354 355 command := c.Args().Get(0) 356 args := c.Args().Slice() 357 358 if srv, err := util.LookupService(command); err != nil { 359 return util.CliError(err) 360 } else if srv != nil && util.ShouldRenderHelp(args) { 361 return cli.Exit(util.FormatServiceUsage(srv, c), 0) 362 } else if srv != nil { 363 err := util.CallService(srv, args) 364 return util.CliError(err) 365 } 366 367 return nil 368 } 369 }