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:
parent
0a0b0b617b
commit
942d242c03
17 changed files with 936 additions and 145 deletions
|
|
@ -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])
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue