1// SPDX-License-Identifier: MIT
2
3package main
4
5import (
6 "context"
7 "errors"
8 "fmt"
9 "os"
10 "strings"
11 "time"
12
13 "github.com/bnema/zerowrap"
14
15 "bnema.dev/ermite-runner/internal/execstream"
16 "bnema.dev/ermite-runner/internal/execstream/execpb"
17 "bnema.dev/ermite-runner/internal/sandbox"
18 "bnema.dev/ermite-runner/internal/sandbox/drivers"
19)
20
21// sandboxCheck runs one stub job in the sandbox chosen by
22// ERMITE_RUNNER_ISOLATION: reap leftovers, start, stream the output to stdout
23// and stderr, stop. Its exit code is the script's. It is the manual counterpart
24// of the integration tests and the smoke test of an installation.
25func sandboxCheck(ctx context.Context, args []string) int {
26 log := zerowrap.FromCtx(ctx)
27 cfg, err := sandbox.LoadConfig(os.Getenv)
28 if err != nil {
29 fmt.Fprintln(os.Stderr, "ermite-runner:", err)
30 return 2
31 }
32 drv, err := sandbox.Select(ctx, cfg, drivers.Factories(), sandbox.Prober{})
33 if err != nil {
34 fmt.Fprintln(os.Stderr, "ermite-runner:", err)
35 return 1
36 }
37 defer func() { _ = drv.Close() }()
38 if err := drv.Reap(ctx); err != nil {
39 log.Warn().Err(err).Msg("reaping leftovers of a previous run")
40 }
41 sb, err := drv.New()
42 if err != nil {
43 fmt.Fprintln(os.Stderr, "ermite-runner:", err)
44 return 1
45 }
46 // Stop runs even when the job context was cancelled.
47 defer func() {
48 sctx, cancel := context.WithTimeout(context.WithoutCancel(ctx), 60*time.Second)
49 defer cancel()
50 if err := sb.Stop(sctx); err != nil {
51 fmt.Fprintln(os.Stderr, "ermite-runner: cleanup:", err)
52 if errors.Is(err, sandbox.ErrLeaked) {
53 fmt.Fprintln(os.Stderr, "ermite-runner: something leaked: run again (the start reaps) or check the state directory")
54 }
55 }
56 }()
57 conn, err := sb.Start(ctx, sandbox.JobSpec{JobID: "sandbox-check"})
58 if err != nil {
59 fmt.Fprintln(os.Stderr, "ermite-runner:", err)
60 return 1
61 }
62 // Ctrl-C / SIGTERM cancel the job: the executor escalates by itself.
63 cancel := make(chan *execpb.Cancel, 1)
64 stop := context.AfterFunc(ctx, func() { cancel <- &execpb.Cancel{Reason: "interrupted"} })
65 defer stop()
66 _, exit, err := execstream.Drive(conn, &execpb.Run{Script: strings.Join(args, " ")}, cancel, func(o *execpb.Output) {
67 f := os.Stdout
68 if o.Stream == execpb.StreamKind_STREAM_KIND_STDERR {
69 f = os.Stderr
70 }
71 _, _ = f.Write(o.Data)
72 })
73 if err != nil {
74 fmt.Fprintln(os.Stderr, "ermite-runner:", err)
75 return 1
76 }
77 if exit.Error != "" {
78 fmt.Fprintln(os.Stderr, "ermite-runner: executor:", exit.Error)
79 }
80 return int(exit.Code)
81}