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 setupExternals(ctx, &cfg)
33 drv, err := sandbox.Select(ctx, cfg, drivers.Factories(), sandbox.Prober{})
34 if err != nil {
35 fmt.Fprintln(os.Stderr, "ermite-runner:", err)
36 return 1
37 }
38 defer func() { _ = drv.Close() }()
39 if err := drv.Reap(ctx); err != nil {
40 log.Warn().Err(err).Msg("reaping leftovers of a previous run")
41 }
42 sb, err := drv.New()
43 if err != nil {
44 fmt.Fprintln(os.Stderr, "ermite-runner:", err)
45 return 1
46 }
47 // Stop runs even when the job context was cancelled.
48 defer func() {
49 sctx, cancel := context.WithTimeout(context.WithoutCancel(ctx), 60*time.Second)
50 defer cancel()
51 if err := sb.Stop(sctx); err != nil {
52 fmt.Fprintln(os.Stderr, "ermite-runner: cleanup:", err)
53 if errors.Is(err, sandbox.ErrLeaked) {
54 fmt.Fprintln(os.Stderr, "ermite-runner: something leaked: run again (the start reaps) or check the state directory")
55 }
56 }
57 }()
58 conn, err := sb.Start(ctx, sandbox.JobSpec{JobID: "sandbox-check"})
59 if err != nil {
60 fmt.Fprintln(os.Stderr, "ermite-runner:", err)
61 return 1
62 }
63 // Ctrl-C / SIGTERM cancel the job: the executor escalates by itself.
64 cancel := make(chan *execpb.Cancel, 1)
65 stop := context.AfterFunc(ctx, func() { cancel <- &execpb.Cancel{Reason: "interrupted"} })
66 defer stop()
67 _, exit, err := execstream.Drive(conn, &execpb.Run{Script: strings.Join(args, " ")}, cancel, func(o *execpb.Output) {
68 f := os.Stdout
69 if o.Stream == execpb.StreamKind_STREAM_KIND_STDERR {
70 f = os.Stderr
71 }
72 _, _ = f.Write(o.Data)
73 })
74 if err != nil {
75 fmt.Fprintln(os.Stderr, "ermite-runner:", err)
76 return 1
77 }
78 if exit.Error != "" {
79 fmt.Fprintln(os.Stderr, "ermite-runner: executor:", exit.Error)
80 }
81 return int(exit.Code)
82}