Go · 4025 bytes Raw Blame History
1 // SPDX-License-Identifier: AGPL-3.0-or-later
2
3 package main
4
5 import (
6 "os"
7 "time"
8
9 "github.com/spf13/cobra"
10 "github.com/spf13/pflag"
11
12 infralog "github.com/tenseleyFlow/shithub/internal/infra/log"
13 runnerpkg "github.com/tenseleyFlow/shithub/internal/runner"
14 runnerapi "github.com/tenseleyFlow/shithub/internal/runner/api"
15 runnerconfig "github.com/tenseleyFlow/shithub/internal/runner/config"
16 "github.com/tenseleyFlow/shithub/internal/runner/engine"
17 "github.com/tenseleyFlow/shithub/internal/runner/workspace"
18 )
19
20 var runConfigPath string
21
22 var runCmd = &cobra.Command{
23 Use: "run",
24 Short: "Claim and execute shithub Actions jobs",
25 RunE: func(cmd *cobra.Command, _ []string) error {
26 cfg, err := runnerconfig.Load(runnerconfig.LoadOptions{
27 ConfigPath: runConfigPath,
28 Overrides: flagOverrides(cmd),
29 })
30 if err != nil {
31 return err
32 }
33 logger := infralog.New(infralog.Options{
34 Level: cfg.Log.Level,
35 Format: cfg.Log.Format,
36 Writer: os.Stderr,
37 }).With("component", "runner")
38
39 workspaces := workspace.New(cfg.Runner.WorkspaceRoot)
40 removed, err := workspaces.Sweep(cfg.Runner.WorkspaceTTL, time.Now().UTC())
41 if err != nil {
42 return err
43 }
44 if removed > 0 {
45 logger.InfoContext(cmd.Context(), "swept stale workspaces", "count", removed)
46 }
47
48 client, err := runnerapi.New(runnerapi.Config{
49 BaseURL: cfg.Server.BaseURL,
50 RunnerToken: cfg.Runner.Token,
51 })
52 if err != nil {
53 return err
54 }
55 execEngine := engine.NewDocker(engine.DockerConfig{
56 Binary: cfg.Engine.Kind,
57 DefaultImage: cfg.Engine.DefaultImage,
58 Network: cfg.Engine.Network,
59 Memory: cfg.Engine.Memory,
60 CPUs: cfg.Engine.CPUs,
61 Stdout: os.Stdout,
62 Stderr: os.Stderr,
63 })
64 r := runnerpkg.New(runnerpkg.Options{
65 API: client,
66 Engine: execEngine,
67 Workspaces: workspaces,
68 Logger: logger,
69 Labels: cfg.Runner.Labels,
70 Capacity: cfg.Runner.Capacity,
71 PollInterval: cfg.Runner.PollInterval,
72 DefaultImage: cfg.Engine.DefaultImage,
73 Clock: func() time.Time { return time.Now().UTC() },
74 })
75 return r.Run(cmd.Context())
76 },
77 }
78
79 func init() {
80 runCmd.Flags().StringVar(&runConfigPath, "config", "", "Path to runner config file")
81 runCmd.Flags().String("server-url", "", "shithub base URL")
82 runCmd.Flags().String("token", "", "Runner registration token")
83 runCmd.Flags().String("labels", "", "Comma-separated runner labels")
84 runCmd.Flags().Int("capacity", 0, "Maximum concurrent jobs this runner advertises")
85 runCmd.Flags().Duration("poll-interval", 0, "Idle heartbeat interval")
86 runCmd.Flags().String("workspace-root", "", "Workspace root directory")
87 runCmd.Flags().Duration("workspace-ttl", 0, "Startup sweep TTL for stale workspaces")
88 runCmd.Flags().String("engine", "", "Execution engine: docker or podman")
89 runCmd.Flags().String("image", "", "Default container image")
90 runCmd.Flags().String("network", "", "Container network")
91 runCmd.Flags().String("memory", "", "Container memory limit")
92 runCmd.Flags().String("cpus", "", "Container CPU limit")
93 runCmd.Flags().String("log-level", "", "Log level: debug, info, warn, error")
94 runCmd.Flags().String("log-format", "", "Log format: text or json")
95 }
96
97 func flagOverrides(cmd *cobra.Command) map[string]string {
98 keys := map[string]string{
99 "server-url": "server.base_url",
100 "token": "runner.token",
101 "labels": "runner.labels",
102 "capacity": "runner.capacity",
103 "poll-interval": "runner.poll_interval",
104 "workspace-root": "runner.workspace_root",
105 "workspace-ttl": "runner.workspace_ttl",
106 "engine": "engine.kind",
107 "image": "engine.default_image",
108 "network": "engine.network",
109 "memory": "engine.memory",
110 "cpus": "engine.cpus",
111 "log-level": "log.level",
112 "log-format": "log.format",
113 }
114 out := make(map[string]string)
115 cmd.Flags().Visit(func(f *pflag.Flag) {
116 if key, ok := keys[f.Name]; ok {
117 out[key] = f.Value.String()
118 }
119 })
120 return out
121 }
122