1// SPDX-License-Identifier: MIT
2
3package main
4
5import (
6 "context"
7 "errors"
8 "flag"
9 "fmt"
10 "os"
11
12 "github.com/bnema/zerowrap"
13
14 "bnema.dev/ermite-api/gen/ermite/ermiteconnect"
15
16 "bnema.dev/ermite-runner/internal/host"
17 "bnema.dev/ermite-runner/internal/sandbox"
18 "bnema.dev/ermite-runner/internal/sandbox/drivers"
19)
20
21// loadHostConfig reads the sandbox settings and the host settings together, so
22// that the state directory and the capacity are the same for both.
23func loadHostConfig() (sandbox.Config, host.Config, error) {
24 scfg, err := sandbox.LoadConfig(os.Getenv)
25 if err != nil {
26 return scfg, host.Config{}, err
27 }
28 name, _ := os.Hostname()
29 hcfg, err := host.LoadConfig(os.Getenv, name, scfg.StateDir, scfg.Capacity, version)
30 return scfg, hcfg, err
31}
32
33func buildDriver(ctx context.Context, scfg sandbox.Config) (sandbox.Driver, error) {
34 return sandbox.Select(ctx, scfg, drivers.Factories(), sandbox.Prober{})
35}
36
37// registerCmd is `ermite-runner register [--token-stdin] [--token T] [--replace]`:
38// it chooses the isolation mode like `run` will, registers the runner with it,
39// and stores the runner token (mode 0600) in the state directory. The register
40// token is read from standard input (--token-stdin) or from
41// ERMITE_RUNNER_REGISTER_TOKEN; --token is accepted but the arguments of a
42// process are visible to every user of the host.
43func registerCmd(ctx context.Context, args []string) int {
44 fs := flag.NewFlagSet("register", flag.ContinueOnError)
45 token := fs.String("token", "", "token with the runner:register scope (visible in the process list: prefer $"+host.EnvRegisterToken+" or --token-stdin)")
46 fromStdin := fs.Bool("token-stdin", false, "read the token from standard input")
47 replace := fs.Bool("replace", false, "replace a runner of the same name")
48 if err := fs.Parse(args); err != nil {
49 return 2
50 }
51 tok, err := host.ResolveRegisterToken(*token, *fromStdin, os.Stdin, os.Getenv)
52 if err != nil {
53 fmt.Fprintln(os.Stderr, "ermite-runner:", err)
54 return 2
55 }
56 *token = tok
57 scfg, hcfg, err := loadHostConfig()
58 if err != nil {
59 fmt.Fprintln(os.Stderr, "ermite-runner:", err)
60 return 2
61 }
62 if hcfg.URL == "" {
63 fmt.Fprintf(os.Stderr, "ermite-runner: %s is not set\n", host.EnvURL)
64 return 2
65 }
66 drv, err := buildDriver(ctx, scfg)
67 if err != nil {
68 fmt.Fprintln(os.Stderr, "ermite-runner:", err)
69 return 1
70 }
71 iso := drv.Isolation()
72 _ = drv.Close()
73
74 client := hcfg.Client(hcfg.URL, *token)
75 st, err := host.Register(ctx, hcfg, client, iso, *replace)
76 if err != nil {
77 fmt.Fprintln(os.Stderr, "ermite-runner:", err)
78 return 1
79 }
80 fmt.Printf("registered runner %q (%s) with isolation %s; token stored in %s\n", st.Name, st.RunnerID, iso, host.StateFile(hcfg.StateDir))
81 return 0
82}
83
84// runCmd is `ermite-runner run`: poll ermite for jobs and run each one in its
85// own sandbox until SIGINT or SIGTERM, then cancel the running jobs with
86// runner_shutdown and report them.
87func runCmd(ctx context.Context) int {
88 log := zerowrap.FromCtx(ctx)
89 scfg, hcfg, err := loadHostConfig()
90 if err != nil {
91 fmt.Fprintln(os.Stderr, "ermite-runner:", err)
92 return 2
93 }
94 st, err := host.LoadState(hcfg.StateDir)
95 if err != nil {
96 fmt.Fprintln(os.Stderr, "ermite-runner:", err)
97 return 2
98 }
99 url := hcfg.URL
100 if url == "" {
101 url = st.URL
102 }
103 if url == "" {
104 fmt.Fprintf(os.Stderr, "ermite-runner: %s is not set\n", host.EnvURL)
105 return 2
106 }
107 drv, err := buildDriver(ctx, scfg)
108 if err != nil {
109 fmt.Fprintln(os.Stderr, "ermite-runner:", err)
110 return 1
111 }
112 defer func() { _ = drv.Close() }()
113 log.Info().Str("runner", st.Name).Str("isolation", string(drv.Isolation())).Int("capacity", scfg.Capacity).Msg("runner started")
114
115 r := &host.Runner{
116 Cfg: hcfg,
117 Driver: drv,
118 Poll: hcfg.Client(url, st.RunnerToken),
119 Job: func(tok string) ermiteconnect.RunnerServiceClient {
120 return hcfg.Client(url, tok)
121 },
122 }
123 r.Cfg.URL = url
124 if err := r.Run(ctx); err != nil {
125 if errors.Is(err, host.ErrRemoved) {
126 fmt.Fprintln(os.Stderr, "ermite-runner:", err)
127 return 3
128 }
129 fmt.Fprintln(os.Stderr, "ermite-runner:", err)
130 return 1
131 }
132 log.Info().Msg("runner stopped")
133 return 0
134}