Move avoidable daemon shell-outs into Go

Reduce the control plane's dependency on helper scripts while keeping the hard Linux integration points in the approved shell-out layer.

Replace the bash-driven image build path with a native Go builder that clones and optionally resizes the rootfs, boots a temporary Firecracker VM, provisions the guest over SSH, installs packages and modules, and preserves the package-manifest sidecar.

Also replace a few small convenience shell-outs with Go helpers: read process stats from /proc, use os.Truncate for ext4 image growth, add file-clone and normalized-line helpers, drop the sh -c work-disk flattening path, and launch Firecracker via a direct sudo command.

Add tests for the new SSH/archive and system helpers, plus a policy test that keeps os/exec imports confined to cli/firecracker/system. Update the docs to describe customize.sh as a manual helper rather than the daemon's image-build backend.

Validated with go mod tidy, go test ./..., and make build.
This commit is contained in:
Thales Maciel 2026-03-17 17:13:07 -03:00
parent 0a0b0b617b
commit 942d242c03
No known key found for this signature in database
GPG key ID: 33112E6833C34679
17 changed files with 936 additions and 145 deletions

View file

@ -7,7 +7,6 @@ import (
"errors"
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
@ -129,31 +128,30 @@ func TestBuildImagePreservesBuildLogOnFailure(t *testing.T) {
}
binDir := t.TempDir()
for _, name := range []string{"sudo", "ip", "curl", "ssh", "jq", "sha256sum", "e2fsck", "resize2fs"} {
for _, name := range []string{"sudo", "ip", "pgrep", "chown", "chmod", "kill", "iptables", "sysctl", "e2fsck", "resize2fs"} {
writeFakeExecutable(t, filepath.Join(binDir, name))
}
bashPath, err := exec.LookPath("bash")
if err != nil {
t.Fatalf("lookpath bash: %v", err)
}
bashWrapper := filepath.Join(binDir, "bash")
if err := os.WriteFile(bashWrapper, []byte(fmt.Sprintf("#!/bin/sh\nexec %q \"$@\"\n", bashPath)), 0o755); err != nil {
t.Fatalf("write bash wrapper: %v", err)
}
t.Setenv("PATH", binDir)
script := filepath.Join(t.TempDir(), "customize.sh")
scriptBody := "#!/bin/sh\necho helper-stdout\necho helper-stderr >&2\nexit 17\n"
if err := os.WriteFile(script, []byte(scriptBody), 0o755); err != nil {
t.Fatalf("write customize script: %v", err)
}
baseRootfs := filepath.Join(t.TempDir(), "base.ext4")
kernelPath := filepath.Join(t.TempDir(), "vmlinux")
for _, path := range []string{baseRootfs, kernelPath} {
packagesPath := filepath.Join(t.TempDir(), "packages.apt")
sshKeyPath := filepath.Join(t.TempDir(), "id_ed25519")
firecrackerBin := filepath.Join(t.TempDir(), "firecracker")
for _, path := range []string{baseRootfs, kernelPath, packagesPath, sshKeyPath} {
if err := os.WriteFile(path, []byte("artifact"), 0o644); err != nil {
t.Fatalf("write %s: %v", path, err)
}
}
if err := os.WriteFile(firecrackerBin, []byte("#!/bin/sh\nexit 0\n"), 0o755); err != nil {
t.Fatalf("write %s: %v", firecrackerBin, err)
}
runner := &scriptedRunner{
t: t,
steps: []runnerStep{
{call: runnerCall{name: "ip", args: []string{"route", "show", "default"}}, out: []byte("default via 192.0.2.1 dev eth0\n")},
},
}
var buf bytes.Buffer
logger, _, err := newDaemonLogger(&buf, "info")
@ -166,12 +164,24 @@ func TestBuildImagePreservesBuildLogOnFailure(t *testing.T) {
ImagesDir: imagesDir,
},
config: model.DaemonConfig{
RuntimeDir: t.TempDir(),
CustomizeScript: script,
DefaultImageName: "default",
RuntimeDir: t.TempDir(),
DefaultImageName: "default",
DefaultPackagesFile: packagesPath,
SSHKeyPath: sshKeyPath,
FirecrackerBin: firecrackerBin,
},
store: store,
runner: runner,
logger: logger,
imageBuild: func(ctx context.Context, spec imageBuildSpec) error {
if _, err := fmt.Fprintln(spec.BuildLog, "builder-stdout"); err != nil {
return err
}
if spec.BaseRootfs != baseRootfs || spec.KernelPath != kernelPath || spec.PackagesPath != packagesPath {
t.Fatalf("unexpected image build spec: %+v", spec)
}
return errors.New("builder failed")
},
}
_, err = d.BuildImage(ctx, api.ImageBuildParams{
@ -194,13 +204,14 @@ func TestBuildImagePreservesBuildLogOnFailure(t *testing.T) {
if readErr != nil {
t.Fatalf("read build log: %v", readErr)
}
if !strings.Contains(string(logData), "helper-stdout") || !strings.Contains(string(logData), "helper-stderr") {
t.Fatalf("build log = %q, want helper stdout/stderr", string(logData))
if !strings.Contains(string(logData), "builder-stdout") {
t.Fatalf("build log = %q, want builder output", string(logData))
}
runner.assertExhausted()
entries := parseLogEntries(t, buf.Bytes())
if !hasLogEntry(entries, map[string]string{"msg": "operation stage", "operation": "image.build", "stage": "launch_helper"}) {
t.Fatalf("expected launch_helper log, got %v", entries)
if !hasLogEntry(entries, map[string]string{"msg": "operation stage", "operation": "image.build", "stage": "launch_builder"}) {
t.Fatalf("expected launch_builder log, got %v", entries)
}
if !strings.Contains(buf.String(), buildLogs[0]) {
t.Fatalf("daemon logs = %q, want build log path %s", buf.String(), buildLogs[0])