Three test seams were still package-level mutable vars, which tests
had to swap before use. That's the classic path to flaky parallel
tests — two goroutines fighting over the same global fake. Push each
down to the struct that owns the behaviour.
internal/daemon/dns_routing.go
lookupExecutableFunc + vmDNSAddrFunc → fields on *HostNetwork,
defaulted at newHostNetwork time. dns_routing_test builds
HostNetwork{..., lookupExecutable: stub, vmDNSAddr: stub} inline,
no more t.Cleanup dance around package-level vars.
internal/daemon/preflight.go + doctor.go
vsockHostDevicePath (mutable string) → vsockHostDevice field on
*VMService, defaulted via defaultVsockHostDevice constant in
newVMService. Preflight reads s.vsockHostDevice; doctor reads
d.vm.vsockHostDevice. Logger test sets d.vm.vsockHostDevice = tmp
after wireServices.
internal/daemon/workspace/workspace.go
HostCommandOutputFunc → *Inspector struct with a Runner field.
Every git-using helper (GitOutput, GitTrimmedOutput,
GitResolvedConfigValue, RunHostCommand, ListSubmodules,
ListOverlayPaths, CountUntrackedPaths, InspectRepo,
ImportRepoToGuest, PrepareRepoCopy) is now a method on *Inspector.
NewInspector() wraps the real host runner for production;
WorkspaceService holds one via repoInspector, CLI deps holds one
too. cli_test.go's submodule-rejection test builds its own
Inspector with a scripted Runner instead of patching a global.
Pure helpers (FinalizeScript, ResolveSourcePath, ParsePrepareMode,
ShellQuote, FormatStepError, GitFileURL, ParseNullSeparatedOutput)
stay free functions since they don't touch the host.
Sentinel: grep for HostCommandOutputFunc, lookupExecutableFunc,
vmDNSAddrFunc, vsockHostDevicePath is now empty across internal/.
make lint test green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
77 lines
2.8 KiB
Go
77 lines
2.8 KiB
Go
package daemon
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
|
|
"banger/internal/model"
|
|
"banger/internal/system"
|
|
)
|
|
|
|
// defaultVsockHostDevice is the vhost-vsock device file every
|
|
// Firecracker guest relies on to talk to the host via vsock. Tests
|
|
// point at a tempfile by setting VMService.vsockHostDevice; production
|
|
// wiring defaults the field to this path in wireServices.
|
|
const defaultVsockHostDevice = "/dev/vhost-vsock"
|
|
|
|
func (s *VMService) validateStartPrereqs(ctx context.Context, vm model.VMRecord, image model.Image) error {
|
|
checks := system.NewPreflight()
|
|
s.addBaseStartPrereqs(checks, image)
|
|
s.capHooks.addStartPrereqs(ctx, checks, vm, image)
|
|
return checks.Err("vm start preflight failed")
|
|
}
|
|
|
|
func (s *VMService) validateWorkDiskResizePrereqs() error {
|
|
checks := system.NewPreflight()
|
|
checks.RequireCommand("truncate", toolHint("truncate"))
|
|
checks.RequireCommand("e2fsck", `install e2fsprogs`)
|
|
checks.RequireCommand("resize2fs", `install e2fsprogs`)
|
|
return checks.Err("work disk resize preflight failed")
|
|
}
|
|
|
|
func (s *VMService) addBaseStartPrereqs(checks *system.Preflight, image model.Image) {
|
|
s.addBaseStartCommandPrereqs(checks)
|
|
checks.RequireExecutable(s.config.FirecrackerBin, "firecracker binary", `install firecracker or set "firecracker_bin"`)
|
|
if helper, err := vsockAgentBinary(s.layout); err == nil {
|
|
checks.RequireExecutable(helper, "vsock agent helper", `run 'make build' or reinstall banger`)
|
|
} else {
|
|
checks.Addf("%v", err)
|
|
}
|
|
checks.RequireFile(s.vsockHostDevice, "vsock host device", "load the vhost_vsock kernel module on the host")
|
|
checks.RequireFile(image.RootfsPath, "rootfs image", "select a valid registered image")
|
|
checks.RequireFile(image.KernelPath, "kernel image", `re-register or rebuild the image with a valid kernel`)
|
|
if strings.TrimSpace(image.InitrdPath) != "" {
|
|
checks.RequireFile(image.InitrdPath, "initrd image", `re-register or rebuild the image with a valid initrd`)
|
|
}
|
|
}
|
|
|
|
func (s *VMService) addBaseStartCommandPrereqs(checks *system.Preflight) {
|
|
for _, command := range []string{"sudo", "ip", "dmsetup", "losetup", "blockdev", "truncate", "pgrep", "chown", "chmod", "kill", "e2cp", "e2rm", "debugfs"} {
|
|
checks.RequireCommand(command, toolHint(command))
|
|
}
|
|
}
|
|
|
|
func toolHint(command string) string {
|
|
switch command {
|
|
case "ip":
|
|
return "install iproute2"
|
|
case "iptables":
|
|
return "install iptables"
|
|
case "sysctl", "losetup", "blockdev", "mount", "umount":
|
|
return "install util-linux"
|
|
case "dmsetup":
|
|
return "install device-mapper"
|
|
case "pgrep", "kill":
|
|
return "install procps"
|
|
case "chown", "chmod", "cp", "truncate":
|
|
return "install coreutils"
|
|
case "e2fsck", "resize2fs", "debugfs", "mkfs.ext4":
|
|
return "install e2fsprogs"
|
|
case "e2cp", "e2rm":
|
|
return "install e2tools"
|
|
case "sudo":
|
|
return "install sudo"
|
|
default:
|
|
return ""
|
|
}
|
|
}
|