mirror of
https://github.com/j3ssie/osmedeus.git
synced 2026-08-21 15:12:29 +02:00
178 lines
3.8 KiB
Go
178 lines
3.8 KiB
Go
package cli
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"os/signal"
|
|
"syscall"
|
|
"time"
|
|
|
|
"github.com/j3ssie/osmedeus/v5/internal/config"
|
|
"github.com/j3ssie/osmedeus/v5/internal/distributed"
|
|
"github.com/j3ssie/osmedeus/v5/internal/terminal"
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
var printer = terminal.NewPrinter()
|
|
|
|
var redisURL string
|
|
|
|
// workerCmd represents the worker command
|
|
var workerCmd = &cobra.Command{
|
|
Use: "worker",
|
|
Short: "Worker node commands for distributed scanning",
|
|
Long: UsageWorker(),
|
|
}
|
|
|
|
// workerJoinCmd joins the worker pool
|
|
var workerJoinCmd = &cobra.Command{
|
|
Use: "join",
|
|
Short: "Join as a worker node",
|
|
Long: UsageWorkerJoin(),
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
cfg := config.Get()
|
|
if cfg == nil {
|
|
return errConfigNotLoaded
|
|
}
|
|
|
|
// Override Redis config from URL if provided
|
|
if redisURL != "" {
|
|
redisCfg, err := distributed.ParseRedisURL(redisURL)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
cfg.Redis = *redisCfg
|
|
}
|
|
|
|
// Check Redis is configured
|
|
if !cfg.IsRedisConfigured() {
|
|
return errRedisNotConfigured
|
|
}
|
|
|
|
// Create worker
|
|
worker, err := distributed.NewWorker(cfg)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Setup graceful shutdown
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
|
|
sigCh := make(chan os.Signal, 1)
|
|
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
|
|
|
|
go func() {
|
|
<-sigCh
|
|
cancel()
|
|
}()
|
|
|
|
// Run worker
|
|
return worker.Run(ctx)
|
|
},
|
|
}
|
|
|
|
// workerStatusCmd shows worker status
|
|
var workerStatusCmd = &cobra.Command{
|
|
Use: "status",
|
|
Short: "Show worker pool status",
|
|
Long: UsageWorkerStatus(),
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
cfg := config.Get()
|
|
if cfg == nil {
|
|
return errConfigNotLoaded
|
|
}
|
|
|
|
// Override Redis config from URL if provided
|
|
if redisURL != "" {
|
|
redisCfg, err := distributed.ParseRedisURL(redisURL)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
cfg.Redis = *redisCfg
|
|
}
|
|
|
|
// Check Redis is configured
|
|
if !cfg.IsRedisConfigured() {
|
|
return errRedisNotConfigured
|
|
}
|
|
|
|
// Create master client to query workers
|
|
master, err := distributed.NewMaster(cfg)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ctx := context.Background()
|
|
workers, err := master.ListWorkers(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if len(workers) == 0 {
|
|
printer.Info("No workers connected")
|
|
return nil
|
|
}
|
|
|
|
// Print workers
|
|
printer.Section("Connected Workers")
|
|
headers := []string{"ID", "Hostname", "Status", "Tasks Done", "Tasks Failed", "Last Heartbeat"}
|
|
var rows [][]string
|
|
|
|
for _, w := range workers {
|
|
rows = append(rows, []string{
|
|
w.ID,
|
|
w.Hostname,
|
|
w.Status,
|
|
formatInt(w.TasksComplete),
|
|
formatInt(w.TasksFailed),
|
|
formatHeartbeat(w.LastHeartbeat),
|
|
})
|
|
}
|
|
|
|
printMarkdownTable(headers, rows)
|
|
return nil
|
|
},
|
|
}
|
|
|
|
var errConfigNotLoaded = &exitError{message: "configuration not loaded", code: 1}
|
|
var errRedisNotConfigured = &exitError{message: "redis not configured. Add redis section to osm-settings.yaml or use --redis-url", code: 1}
|
|
|
|
type exitError struct {
|
|
message string
|
|
code int
|
|
}
|
|
|
|
func (e *exitError) Error() string {
|
|
return e.message
|
|
}
|
|
|
|
func init() {
|
|
workerJoinCmd.Flags().StringVar(&redisURL, "redis-url", "", "Redis connection URL (overrides settings)")
|
|
workerStatusCmd.Flags().StringVar(&redisURL, "redis-url", "", "Redis connection URL (overrides settings)")
|
|
|
|
workerCmd.AddCommand(workerJoinCmd)
|
|
workerCmd.AddCommand(workerStatusCmd)
|
|
}
|
|
|
|
// formatInt formats an integer for display
|
|
func formatInt(n int) string {
|
|
return fmt.Sprintf("%d", n)
|
|
}
|
|
|
|
// formatHeartbeat formats a time as a relative duration
|
|
func formatHeartbeat(t time.Time) string {
|
|
if t.IsZero() {
|
|
return "never"
|
|
}
|
|
d := time.Since(t)
|
|
if d < time.Minute {
|
|
return fmt.Sprintf("%ds ago", int(d.Seconds()))
|
|
}
|
|
if d < time.Hour {
|
|
return fmt.Sprintf("%dm ago", int(d.Minutes()))
|
|
}
|
|
return fmt.Sprintf("%dh ago", int(d.Hours()))
|
|
}
|