Go · 4924 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 SeccompProfile: cfg.Engine.SeccompProfile,
62 User: cfg.Engine.User,
63 PidsLimit: cfg.Engine.PidsLimit,
64 DNSServers: cfg.Engine.DNSServers,
65 Stdout: os.Stdout,
66 Stderr: os.Stderr,
67 Logger: logger,
68 })
69 r := runnerpkg.New(runnerpkg.Options{
70 API: client,
71 Engine: execEngine,
72 Workspaces: workspaces,
73 Logger: logger,
74 Labels: cfg.Runner.Labels,
75 Capacity: cfg.Runner.Capacity,
76 PollInterval: cfg.Runner.PollInterval,
77 DefaultImage: cfg.Engine.DefaultImage,
78 Clock: func() time.Time { return time.Now().UTC() },
79 })
80 return r.Run(cmd.Context())
81 },
82 }
83
84 func init() {
85 runCmd.Flags().StringVar(&runConfigPath, "config", "", "Path to runner config file")
86 runCmd.Flags().String("server-url", "", "shithub base URL")
87 runCmd.Flags().String("token", "", "Runner registration token")
88 runCmd.Flags().String("labels", "", "Comma-separated runner labels")
89 runCmd.Flags().Int("capacity", 0, "Maximum concurrent jobs this runner advertises")
90 runCmd.Flags().Duration("poll-interval", 0, "Idle heartbeat interval")
91 runCmd.Flags().String("workspace-root", "", "Workspace root directory")
92 runCmd.Flags().Duration("workspace-ttl", 0, "Startup sweep TTL for stale workspaces")
93 runCmd.Flags().String("engine", "", "Execution engine: docker or podman")
94 runCmd.Flags().String("image", "", "Default container image")
95 runCmd.Flags().String("network", "", "Container network")
96 runCmd.Flags().String("memory", "", "Container memory limit")
97 runCmd.Flags().String("cpus", "", "Container CPU limit")
98 runCmd.Flags().String("seccomp-profile", "", "Container seccomp profile path")
99 runCmd.Flags().String("container-user", "", "Default container user")
100 runCmd.Flags().Int("pids-limit", 0, "Container PID limit")
101 runCmd.Flags().String("network-allowlist", "", "Comma-separated host patterns allowed by the runner DNS policy")
102 runCmd.Flags().String("dns-servers", "", "Comma-separated DNS servers passed to step containers")
103 runCmd.Flags().String("log-level", "", "Log level: debug, info, warn, error")
104 runCmd.Flags().String("log-format", "", "Log format: text or json")
105 }
106
107 func flagOverrides(cmd *cobra.Command) map[string]string {
108 keys := map[string]string{
109 "server-url": "server.base_url",
110 "token": "runner.token",
111 "labels": "runner.labels",
112 "capacity": "runner.capacity",
113 "poll-interval": "runner.poll_interval",
114 "workspace-root": "runner.workspace_root",
115 "workspace-ttl": "runner.workspace_ttl",
116 "engine": "engine.kind",
117 "image": "engine.default_image",
118 "network": "engine.network",
119 "memory": "engine.memory",
120 "cpus": "engine.cpus",
121 "seccomp-profile": "engine.seccomp_profile",
122 "container-user": "engine.user",
123 "pids-limit": "engine.pids_limit",
124 "network-allowlist": "runner.network_allowlist",
125 "dns-servers": "engine.dns_servers",
126 "log-level": "log.level",
127 "log-format": "log.format",
128 }
129 out := make(map[string]string)
130 cmd.Flags().Visit(func(f *pflag.Flag) {
131 if key, ok := keys[f.Name]; ok {
132 out[key] = f.Value.String()
133 }
134 })
135 return out
136 }
137