main.go RawHistoryFolder
1// SPDX-License-Identifier: MIT
2
3// Command ermite-runner runs CI jobs for an ermite server. Each job runs in
4// its own Firecracker micro-VM, or in its own container when the host has no
5// KVM, and executes workflows compatible with GitHub Actions.
6//
7// Subcommands: version; register stores a runner token obtained from ermite;
8// run fetches jobs from ermite and runs each in a sandbox; init and exec run
9// inside a sandbox (guest PID 1, and the executor); sandbox-check runs one stub
10// script through the selected isolation mode and prints its output.
11package main
12
13import (
14 "context"
15 "fmt"
16 "os"
17 "os/signal"
18 "syscall"
19
20 "github.com/bnema/zerowrap"
21
22 "bnema.dev/ermite-runner/internal/guest"
23)
24
25// version is set at build time with -ldflags "-X main.version=...".
26var version = "dev"
27
28func main() {
29 os.Exit(run(os.Args[1:]))
30}
31
32func run(args []string) int {
33 if len(args) == 0 {
34 usage()
35 return 2
36 }
37 log := zerowrap.New(zerowrap.Config{Level: envOr("ERMITE_RUNNER_LOG_LEVEL", "info"), Format: envOr("ERMITE_RUNNER_LOG_FORMAT", "console")})
38 ctx := zerowrap.WithCtx(context.Background(), log)
39 ctx, stop := signal.NotifyContext(ctx, os.Interrupt, syscall.SIGTERM)
40 defer stop()
41
42 switch args[0] {
43 case "version":
44 fmt.Println("ermite-runner", version)
45 return 0
46 case "init":
47 return guest.Init(ctx, guest.InitConfig{Version: version})
48 case "exec":
49 return execCmd(ctx)
50 case "sandbox-check":
51 return sandboxCheck(ctx, args[1:])
52 case "register":
53 return registerCmd(ctx, args[1:])
54 case "run":
55 return runCmd(ctx)
56 }
57 usage()
58 return 2
59}
60
61func usage() {
62 fmt.Fprintln(os.Stderr, "usage: ermite-runner version | register [--token T] [--replace] | run | sandbox-check [script]\n (inside a sandbox: ermite-runner init | exec)")
63}
64
65func envOr(key, def string) string {
66 if v := os.Getenv(key); v != "" {
67 return v
68 }
69 return def
70}
71
72// execCmd is `ermite-runner exec`, the executor of a sandbox.
73func execCmd(ctx context.Context) int {
74 log := zerowrap.FromCtx(ctx)
75 target, err := guest.ParseTarget(os.Getenv("ERMITE_CONNECT"))
76 if err != nil {
77 log.Error().Err(err).Msg("exec")
78 return 1
79 }
80 code, err := guest.Exec(ctx, guest.ExecConfig{
81 Target: target,
82 BootMs: guest.ParseBootMs(os.Getenv("ERMITE_BOOT_MS")),
83 Sandbox: envOr("ERMITE_SANDBOX", "container"),
84 Version: envOr("ERMITE_VERSION", version),
85 // A test image without the user "runner" (ERMITE_RUNNER_ALLOW_ROOT_STEPS=1);
86 // a runner never sets it: the job refuses to start instead.
87 AllowRootSteps: os.Getenv("ERMITE_RUNNER_ALLOW_ROOT_STEPS") == "1",
88 })
89 if err != nil {
90 log.Error().Err(err).Msg("exec")
91 }
92 return code
93}