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

@ -102,12 +102,12 @@ func openLogFile(path string) (*os.File, error) {
}
func buildConfig(cfg MachineConfig) sdk.Config {
drives := sdk.NewDrivesBuilder(
cfg.RootDrivePath,
).
WithRootDrive(cfg.RootDrivePath, sdk.WithDriveID("rootfs"), sdk.WithReadOnly(false)).
AddDrive(cfg.WorkDrivePath, false, sdk.WithDriveID("work")).
Build()
drivesBuilder := sdk.NewDrivesBuilder(cfg.RootDrivePath).
WithRootDrive(cfg.RootDrivePath, sdk.WithDriveID("rootfs"), sdk.WithReadOnly(false))
if strings.TrimSpace(cfg.WorkDrivePath) != "" {
drivesBuilder = drivesBuilder.AddDrive(cfg.WorkDrivePath, false, sdk.WithDriveID("work"))
}
drives := drivesBuilder.Build()
return sdk.Config{
SocketPath: cfg.SocketPath,
@ -132,14 +132,7 @@ func buildConfig(cfg MachineConfig) sdk.Config {
}
func buildProcessRunner(cfg MachineConfig, logFile *os.File) *exec.Cmd {
script := strings.Join([]string{
"umask 000",
"exec " + shellQuote(cfg.BinaryPath) +
" --api-sock " + shellQuote(cfg.SocketPath) +
" --id " + shellQuote(cfg.VMID),
}, " && ")
cmd := exec.Command("sudo", "-n", "sh", "-c", script)
cmd := exec.Command("sudo", "-n", cfg.BinaryPath, "--api-sock", cfg.SocketPath, "--id", cfg.VMID)
cmd.Stdin = nil
if logFile != nil {
cmd.Stdout = logFile
@ -148,10 +141,6 @@ func buildProcessRunner(cfg MachineConfig, logFile *os.File) *exec.Cmd {
return cmd
}
func shellQuote(value string) string {
return "'" + strings.ReplaceAll(value, "'", `'"'"'`) + "'"
}
func newLogger(base *slog.Logger) *logrus.Entry {
logger := logrus.New()
logger.SetOutput(io.Discard)

View file

@ -58,7 +58,7 @@ func TestBuildConfig(t *testing.T) {
}
}
func TestBuildProcessRunnerUsesSudoWrapper(t *testing.T) {
func TestBuildProcessRunnerUsesDirectSudoCommand(t *testing.T) {
cmd := buildProcessRunner(MachineConfig{
BinaryPath: "/repo/firecracker",
SocketPath: "/tmp/fc.sock",
@ -68,14 +68,14 @@ func TestBuildProcessRunnerUsesSudoWrapper(t *testing.T) {
if cmd.Path != "/usr/bin/sudo" && cmd.Path != "sudo" {
t.Fatalf("command path = %q", cmd.Path)
}
if len(cmd.Args) != 5 {
if len(cmd.Args) != 7 {
t.Fatalf("args = %v", cmd.Args)
}
if cmd.Args[1] != "-n" || cmd.Args[2] != "sh" || cmd.Args[3] != "-c" {
t.Fatalf("args = %v", cmd.Args)
}
if want := "umask 000 && exec '/repo/firecracker' --api-sock '/tmp/fc.sock' --id 'vm-1'"; cmd.Args[4] != want {
t.Fatalf("script = %q, want %q", cmd.Args[4], want)
want := []string{"sudo", "-n", "/repo/firecracker", "--api-sock", "/tmp/fc.sock", "--id", "vm-1"}
for i, arg := range want {
if cmd.Args[i] != arg {
t.Fatalf("args[%d] = %q, want %q (all args: %v)", i, cmd.Args[i], arg, cmd.Args)
}
}
if cmd.Cancel != nil {
t.Fatal("process runner should not be tied to a request context")