1// SPDX-License-Identifier: MIT
2
3// Package cmdrun runs host commands (ip, nft, mkfs.ext4) behind a small
4// interface so that callers can be tested with a fake.
5package cmdrun
6
7import (
8 "bytes"
9 "context"
10 "fmt"
11 "io"
12 "os/exec"
13 "strings"
14)
15
16// Runner runs a command to completion and returns its combined output.
17type Runner interface {
18 Run(ctx context.Context, stdin io.Reader, name string, args ...string) ([]byte, error)
19}
20
21// Exec runs real commands.
22type Exec struct{}
23
24// Run implements Runner. A non-zero exit becomes an error carrying the command
25// line and its output.
26func (Exec) Run(ctx context.Context, stdin io.Reader, name string, args ...string) ([]byte, error) {
27 cmd := exec.CommandContext(ctx, name, args...)
28 cmd.Stdin = stdin
29 var out bytes.Buffer
30 cmd.Stdout = &out
31 cmd.Stderr = &out
32 if err := cmd.Run(); err != nil {
33 return out.Bytes(), fmt.Errorf("%s %s: %w: %s", name, strings.Join(args, " "), err, strings.TrimSpace(out.String()))
34 }
35 return out.Bytes(), nil
36}
37
38// Call is one recorded invocation of a Fake.
39type Call struct {
40 Name string
41 Args []string
42 Stdin string
43}
44
45// String renders the call as a shell-like line.
46func (c Call) String() string { return strings.TrimSpace(c.Name + " " + strings.Join(c.Args, " ")) }
47
48// Fake records calls and answers them with Handle (nil means success).
49type Fake struct {
50 Calls []Call
51 Handle func(Call) ([]byte, error)
52}
53
54// Run implements Runner.
55func (f *Fake) Run(_ context.Context, stdin io.Reader, name string, args ...string) ([]byte, error) {
56 c := Call{Name: name, Args: append([]string(nil), args...)}
57 if stdin != nil {
58 b, _ := io.ReadAll(stdin)
59 c.Stdin = string(b)
60 }
61 f.Calls = append(f.Calls, c)
62 if f.Handle != nil {
63 return f.Handle(c)
64 }
65 return nil, nil
66}
67
68// Lines returns the recorded calls as strings.
69func (f *Fake) Lines() []string {
70 out := make([]string, len(f.Calls))
71 for i, c := range f.Calls {
72 out[i] = c.String()
73 }
74 return out
75}