port smoke to go

This commit is contained in:
Thales Maciel 2026-05-01 19:34:44 -03:00
parent b0a9d64f4a
commit 9ed44bfd75
No known key found for this signature in database
GPG key ID: 33112E6833C34679
20 changed files with 2118 additions and 1573 deletions

View file

@ -34,10 +34,14 @@ The most common workflow is one command:
banger vm run bare sandbox, drops into ssh
banger vm run ./repo ships a repo into /root/repo, drops into ssh
banger vm run ./repo -- make test ships a repo, runs the command, exits with its status
banger vm run --rm -- script.sh --rm: VM auto-deletes when the session/command exits
banger vm run --nat ./repo --nat: outbound internet (required when .mise.toml installs tools)
banger vm run -d ./repo --nat -d/--detach: prep workspace + bootstrap, exit without ssh
For a longer-lived VM, use 'banger vm create' to provision and
'banger vm ssh <name>' to attach. 'banger ps' lists running VMs;
'banger vm list --all' shows stopped ones too.
'banger vm list --all' shows stopped ones too. Guests are reachable
at <name>.vm from the host once 'banger ssh-config --install' is run.
First-time setup, in order:
sudo banger system install install the systemd services

View file

@ -588,7 +588,7 @@ func TestRunVMCreatePollsUntilDone(t *testing.T) {
}
var stderr bytes.Buffer
got, err := d.runVMCreate(context.Background(), "/tmp/bangerd.sock", &stderr, api.VMCreateParams{Name: "devbox"})
got, err := d.runVMCreate(context.Background(), "/tmp/bangerd.sock", &stderr, api.VMCreateParams{Name: "devbox"}, false)
if err != nil {
t.Fatalf("d.runVMCreate: %v", err)
}
@ -643,7 +643,7 @@ func TestVMCreateProgressRendererSuppressesDuplicateLines(t *testing.T) {
func TestVMRunProgressRendererSuppressesDuplicateLines(t *testing.T) {
var stderr bytes.Buffer
renderer := newVMRunProgressRenderer(&stderr)
renderer := newVMRunProgressRenderer(&stderr, true)
renderer.render("waiting for guest ssh")
renderer.render("waiting for guest ssh")
@ -661,6 +661,67 @@ func TestVMRunProgressRendererSuppressesDuplicateLines(t *testing.T) {
}
}
// TestVMRunProgressRendererInlineRewrites covers the TTY default: each
// render call rewrites the same line via \r + clear-to-EOL instead of
// emitting a newline, so the user sees one moving status line until
// commitLine / clear / the caller's own newline closes it out.
func TestVMRunProgressRendererInlineRewrites(t *testing.T) {
var stderr bytes.Buffer
renderer := &vmRunProgressRenderer{out: &stderr, enabled: true, inline: true}
renderer.render("waiting for guest ssh")
renderer.render("preparing guest workspace")
renderer.commitLine("vm devbox running; reconnect with: banger vm ssh devbox")
got := stderr.String()
wantPrefix := "\r\x1b[K[vm run] waiting for guest ssh" +
"\r\x1b[K[vm run] preparing guest workspace" +
"\r\x1b[K[vm run] vm devbox running; reconnect with: banger vm ssh devbox\n"
if got != wantPrefix {
t.Fatalf("inline output = %q, want %q", got, wantPrefix)
}
}
// TestVMRunProgressRendererClearWipesActiveLine guards the path used
// before sshExec/runSSHSession: clear() must erase the live inline
// line so the next writer (the ssh session, a warning, the user's
// command output) starts from column 0 without a trailing status.
func TestVMRunProgressRendererClearWipesActiveLine(t *testing.T) {
var stderr bytes.Buffer
renderer := &vmRunProgressRenderer{out: &stderr, enabled: true, inline: true}
renderer.render("attaching to guest")
renderer.clear()
// clear() on an already-cleared renderer is a no-op (active=false).
renderer.clear()
got := stderr.String()
want := "\r\x1b[K[vm run] attaching to guest\r\x1b[K"
if got != want {
t.Fatalf("after clear stderr = %q, want %q", got, want)
}
}
// TestVMCreateProgressRendererInlineRewrites mirrors the vm_run inline
// test for the create-side renderer so both progress paths stay in
// sync if either is touched in isolation.
func TestVMCreateProgressRendererInlineRewrites(t *testing.T) {
var stderr bytes.Buffer
renderer := &vmCreateProgressRenderer{out: &stderr, enabled: true, inline: true}
renderer.render(api.VMCreateOperation{Stage: "prepare_work_disk", Detail: "cloning work seed"})
renderer.render(api.VMCreateOperation{Stage: "wait_vsock_agent", Detail: "waiting for guest vsock agent"})
renderer.clear()
got := stderr.String()
want := "\r\x1b[K[vm create] preparing work disk: cloning work seed" +
"\r\x1b[K[vm create] waiting for vsock agent: waiting for guest vsock agent" +
"\r\x1b[K"
if got != want {
t.Fatalf("inline output = %q, want %q", got, want)
}
}
func TestWithHeartbeatNoOpForNonTTY(t *testing.T) {
var buf bytes.Buffer
called := false
@ -1326,6 +1387,7 @@ func TestRunVMRunWorkspacePreparesAndAttaches(t *testing.T) {
false,
false,
false,
false,
)
if err != nil {
t.Fatalf("d.runVMRun: %v", err)
@ -1404,6 +1466,7 @@ func TestVMRunPrintsPostCreateProgress(t *testing.T) {
false,
false,
false,
false,
)
if err != nil {
t.Fatalf("d.runVMRun: %v", err)
@ -1481,6 +1544,7 @@ func TestRunVMRunWarnsWhenToolingHarnessStartFails(t *testing.T) {
false,
false,
false,
false,
)
if err != nil {
t.Fatalf("d.runVMRun: %v", err)
@ -1534,6 +1598,7 @@ func TestRunVMRunBareModeSkipsWorkspaceAndTooling(t *testing.T) {
false,
false,
false,
false,
)
if err != nil {
t.Fatalf("d.runVMRun: %v", err)
@ -1580,6 +1645,7 @@ func TestRunVMRunRMDeletesAfterSessionExits(t *testing.T) {
true, // --rm,
false,
false,
false,
)
if err != nil {
t.Fatalf("d.runVMRun: %v", err)
@ -1632,6 +1698,7 @@ func TestRunVMRunRMSkipsDeleteOnSSHWaitTimeout(t *testing.T) {
true, // --rm,
false,
false,
false,
)
if err == nil {
t.Fatal("want timeout error")
@ -1676,6 +1743,7 @@ func TestRunVMRunSSHTimeoutReturnsActionableError(t *testing.T) {
false,
false,
false,
false,
)
if err == nil {
t.Fatal("want timeout error")
@ -1727,6 +1795,7 @@ func TestRunVMRunCommandModePropagatesExitCode(t *testing.T) {
false,
false,
false,
false,
)
var exitErr ExitCodeError
if !errors.As(err, &exitErr) || exitErr.Code != 7 {

View file

@ -35,8 +35,11 @@ provisions ssh, and drops you into the guest in one command. Use
longer-lived VM you'll come back to.
Quick reference:
banger vm run ephemeral sandbox; --rm to delete on exit
banger vm run ./repo -- make test ship a repo, run a command, exit
banger vm run interactive sandbox (stays alive on disconnect)
banger vm run --rm -- script.sh ephemeral: VM auto-deletes on exit
banger vm run ./repo -- make test ship a repo, run a command, exit with its status
banger vm run --nat ./repo --nat: outbound internet (required for mise bootstrap)
banger vm run -d ./repo --nat -d/--detach: prep + bootstrap, exit (no ssh attach)
banger vm create --name dev persistent VM; pair with 'vm ssh'
banger vm ssh <name> open a shell in a running VM
banger vm exec <name> -- make test run a command in the workspace with mise toolchain
@ -45,6 +48,7 @@ Quick reference:
banger vm delete <name> stop + remove disks
banger ps / banger vm list running / all VMs (use --all)
banger vm logs <name> guest console + daemon log
banger vm set --nat <name> toggle NAT on an existing VM (--no-nat to remove)
banger vm workspace prepare/export ship a repo in, pull diffs back
`),
Example: strings.TrimSpace(`
@ -93,6 +97,7 @@ func (d *deps) newVMRunCommand() *cobra.Command {
dryRun bool
detach bool
skipBootstrap bool
verbose bool
)
cmd := &cobra.Command{
Use: "run [path] [-- command args...]",
@ -103,14 +108,33 @@ Create a sandbox VM and either drop into an interactive shell or run a command.
Modes:
banger vm run bare sandbox, drops into ssh
banger vm run ./repo workspace sandbox, drops into ssh at /root/repo
banger vm run ./repo -- make test workspace, runs command, exits with its status
banger vm run -d ./repo workspace + bootstrap, exit (no ssh attach)
banger vm run ./repo -- make test workspace + run command, exit with its status
banger vm run --rm -- script.sh ephemeral: VM auto-deletes when the session/command exits
banger vm run -d ./repo workspace + bootstrap, exit (reconnect with 'vm ssh')
Workspace mode (path argument):
Passing a path copies the repo's git-tracked files into /root/repo
inside the guest. Untracked files are skipped by default pass
--include-untracked to ship them too, or --dry-run to preview the
file list without creating a VM.
Outbound internet (--nat):
Guests have no internet access by default. Pass --nat to enable
host-side MASQUERADE so the VM can reach the public network. NAT is
required whenever the workspace declares mise tooling (see below).
Toggle on an existing VM with 'banger vm set --nat <name>'.
Tooling bootstrap (workspace mode):
When the workspace contains a .mise.toml or .tool-versions, vm run
installs the listed tools via mise on first boot. The bootstrap
needs internet, so --nat must be set. Pass --no-bootstrap to skip
it entirely (no NAT requirement).
Exit behaviour:
In command mode (-- <cmd>), the guest command's exit code propagates
through banger. Without --rm, the VM stays alive after the session
or command exits reconnect with 'banger vm ssh <name>'. With --rm,
the VM is deleted on exit (stdout/stderr are preserved).
`),
Args: cobra.ArbitraryArgs,
Example: strings.TrimSpace(`
@ -190,7 +214,7 @@ Tooling bootstrap (workspace mode):
if err != nil {
return err
}
return d.runVMRun(cmd.Context(), layout.SocketPath, cfg, cmd.InOrStdin(), cmd.OutOrStdout(), cmd.ErrOrStderr(), params, repoPtr, commandArgs, removeOnExit, detach, skipBootstrap)
return d.runVMRun(cmd.Context(), layout.SocketPath, cfg, cmd.InOrStdin(), cmd.OutOrStdout(), cmd.ErrOrStderr(), params, repoPtr, commandArgs, removeOnExit, detach, skipBootstrap, verbose)
},
}
cmd.Flags().StringVar(&name, "name", "", "vm name")
@ -199,14 +223,15 @@ Tooling bootstrap (workspace mode):
cmd.Flags().IntVar(&memory, "memory", defaults.MemoryMiB, "memory in MiB")
cmd.Flags().StringVar(&systemOverlaySize, "system-overlay-size", model.FormatSizeBytes(defaults.SystemOverlaySizeByte), "system overlay size")
cmd.Flags().StringVar(&workDiskSize, "disk-size", model.FormatSizeBytes(defaults.WorkDiskSizeBytes), "work disk size")
cmd.Flags().BoolVar(&natEnabled, "nat", false, "enable NAT")
cmd.Flags().BoolVar(&natEnabled, "nat", false, "enable outbound internet from the guest (host-side MASQUERADE; required when the workspace declares mise tooling)")
cmd.Flags().StringVar(&branchName, "branch", "", "create and switch to a new guest branch")
cmd.Flags().StringVar(&fromRef, "from", "HEAD", "git ref to branch from when --branch is set (default: HEAD)")
cmd.Flags().BoolVar(&removeOnExit, "rm", false, "delete the VM after the ssh session / command exits")
cmd.Flags().BoolVar(&removeOnExit, "rm", false, "ephemeral mode: delete the VM (and its disks) after the ssh session / command exits")
cmd.Flags().BoolVar(&includeUntracked, "include-untracked", false, "also copy untracked non-ignored files into the guest workspace (default: tracked files only)")
cmd.Flags().BoolVar(&dryRun, "dry-run", false, "list the files that would be copied into the guest workspace and exit without creating a VM")
cmd.Flags().BoolVarP(&detach, "detach", "d", false, "create the VM, prep workspace + bootstrap, exit without attaching to ssh")
cmd.Flags().BoolVarP(&detach, "detach", "d", false, "detached mode: create the VM, run workspace prep + bootstrap synchronously, exit without ssh attach (reconnect with 'vm ssh')")
cmd.Flags().BoolVar(&skipBootstrap, "no-bootstrap", false, "skip the mise tooling bootstrap (no --nat requirement)")
cmd.Flags().BoolVarP(&verbose, "verbose", "v", false, "show every progress line instead of a single rewriting status line")
_ = cmd.RegisterFlagCompletionFunc("image", d.completeImageNames)
return cmd
}
@ -370,6 +395,7 @@ func (d *deps) newVMCreateCommand() *cobra.Command {
workDiskSize = model.FormatSizeBytes(defaults.WorkDiskSizeBytes)
natEnabled bool
noStart bool
verbose bool
)
cmd := &cobra.Command{
Use: "create",
@ -397,7 +423,7 @@ Use 'vm create' for a longer-lived VM you'll come back to. Use
if err != nil {
return err
}
vm, err := d.runVMCreate(cmd.Context(), layout.SocketPath, cmd.ErrOrStderr(), params)
vm, err := d.runVMCreate(cmd.Context(), layout.SocketPath, cmd.ErrOrStderr(), params, verbose)
if err != nil {
return err
}
@ -410,8 +436,9 @@ Use 'vm create' for a longer-lived VM you'll come back to. Use
cmd.Flags().IntVar(&memory, "memory", defaults.MemoryMiB, "memory in MiB")
cmd.Flags().StringVar(&systemOverlaySize, "system-overlay-size", model.FormatSizeBytes(defaults.SystemOverlaySizeByte), "system overlay size")
cmd.Flags().StringVar(&workDiskSize, "disk-size", model.FormatSizeBytes(defaults.WorkDiskSizeBytes), "work disk size")
cmd.Flags().BoolVar(&natEnabled, "nat", false, "enable NAT")
cmd.Flags().BoolVar(&natEnabled, "nat", false, "enable outbound internet from the guest (host-side MASQUERADE)")
cmd.Flags().BoolVar(&noStart, "no-start", false, "create without starting")
cmd.Flags().BoolVarP(&verbose, "verbose", "v", false, "show every progress line instead of a single rewriting status line")
_ = cmd.RegisterFlagCompletionFunc("image", d.completeImageNames)
return cmd
}

View file

@ -61,14 +61,14 @@ func printVMSpecLine(out io.Writer, params api.VMCreateParams) {
// gets the spec line up front and the progress renderer thereafter.
// On context cancel we cooperate with the daemon to cancel the
// in-flight op so it doesn't leak partially-created VM state.
func (d *deps) runVMCreate(ctx context.Context, socketPath string, stderr io.Writer, params api.VMCreateParams) (model.VMRecord, error) {
func (d *deps) runVMCreate(ctx context.Context, socketPath string, stderr io.Writer, params api.VMCreateParams, verbose bool) (model.VMRecord, error) {
start := time.Now()
printVMSpecLine(stderr, params)
begin, err := d.vmCreateBegin(ctx, socketPath, params)
if err != nil {
return model.VMRecord{}, err
}
renderer := newVMCreateProgressRenderer(stderr)
renderer := newVMCreateProgressRenderer(stderr, verbose)
renderer.render(begin.Operation)
op := begin.Operation
@ -76,6 +76,7 @@ func (d *deps) runVMCreate(ctx context.Context, socketPath string, stderr io.Wri
if op.Done {
renderer.render(op)
if op.Success && op.VM != nil {
renderer.clear()
elapsed := formatVMCreateElapsed(time.Since(start))
_, _ = fmt.Fprintf(stderr, "[vm create] ready in %s\n", style.Dim(stderr, elapsed))
return *op.VM, nil
@ -113,13 +114,22 @@ func (d *deps) runVMCreate(ctx context.Context, socketPath string, stderr io.Wri
type vmCreateProgressRenderer struct {
out io.Writer
enabled bool
inline bool
active bool
lastLine string
}
func newVMCreateProgressRenderer(out io.Writer) *vmCreateProgressRenderer {
// newVMCreateProgressRenderer wires up progress for `vm create`. On
// non-TTY writers it stays disabled (CI/test logs already capture the
// spec + ready lines); on TTY it rewrites a single line via \r unless
// verbose is set or BANGER_NO_PROGRESS is exported, in which case it
// falls back to one line per stage.
func newVMCreateProgressRenderer(out io.Writer, verbose bool) *vmCreateProgressRenderer {
tty := writerSupportsProgress(out)
return &vmCreateProgressRenderer{
out: out,
enabled: writerSupportsProgress(out),
enabled: tty,
inline: tty && !verbose && !progressDisabledByEnv(),
}
}
@ -132,9 +142,32 @@ func (r *vmCreateProgressRenderer) render(op api.VMCreateOperation) {
return
}
r.lastLine = line
if r.inline {
_, _ = fmt.Fprint(r.out, "\r\x1b[K", line)
r.active = true
return
}
_, _ = fmt.Fprintln(r.out, line)
}
// clear resets the live inline line so the caller can write a clean
// terminating message. No-op outside inline mode.
func (r *vmCreateProgressRenderer) clear() {
if r == nil || !r.enabled || !r.inline || !r.active {
return
}
_, _ = fmt.Fprint(r.out, "\r\x1b[K")
r.active = false
r.lastLine = ""
}
// progressDisabledByEnv is the BANGER_NO_PROGRESS escape hatch — a
// non-empty value forces line-per-stage output even on a TTY, so users
// can pipe `script(1)` / tmux capture without \r artifacts.
func progressDisabledByEnv() bool {
return strings.TrimSpace(os.Getenv("BANGER_NO_PROGRESS")) != ""
}
// writerSupportsProgress returns true only when out is a terminal.
// Keeps stage lines + heartbeat dots out of piped / logged output
// where they'd just be noise.

View file

@ -149,7 +149,7 @@ func splitVMRunArgs(cmd *cobra.Command, args []string) (pathArgs, commandArgs []
// for guest ssh, optionally materialise a workspace and kick off the
// tooling bootstrap, then either attach interactively or run the
// user's command and propagate its exit status.
func (d *deps) runVMRun(ctx context.Context, socketPath string, cfg model.DaemonConfig, stdin io.Reader, stdout, stderr io.Writer, params api.VMCreateParams, repo *vmRunRepo, command []string, removeOnExit, detach, skipBootstrap bool) error {
func (d *deps) runVMRun(ctx context.Context, socketPath string, cfg model.DaemonConfig, stdin io.Reader, stdout, stderr io.Writer, params api.VMCreateParams, repo *vmRunRepo, command []string, removeOnExit, detach, skipBootstrap, verbose bool) error {
if repo != nil && !skipBootstrap && !params.NATEnabled {
hasMise, err := repoHasMiseFiles(repo.sourcePath)
if err != nil {
@ -159,8 +159,9 @@ func (d *deps) runVMRun(ctx context.Context, socketPath string, cfg model.Daemon
return errors.New("tooling bootstrap requires --nat (or pass --no-bootstrap to skip)")
}
}
progress := newVMRunProgressRenderer(stderr)
vm, err := d.runVMCreate(ctx, socketPath, stderr, params)
progress := newVMRunProgressRenderer(stderr, verbose)
defer progress.clear()
vm, err := d.runVMCreate(ctx, socketPath, stderr, params, verbose)
if err != nil {
return err
}
@ -183,8 +184,10 @@ func (d *deps) runVMRun(ctx context.Context, socketPath string, cfg model.Daemon
cleanupCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if err := d.vmDelete(cleanupCtx, socketPath, vmRef); err != nil {
progress.clear()
printVMRunWarning(stderr, fmt.Sprintf("--rm cleanup failed: %v (leaked vm %q; delete manually)", err, vmRef))
} else if err := removeUserKnownHosts(vm); err != nil {
progress.clear()
printVMRunWarning(stderr, fmt.Sprintf("known_hosts cleanup failed: %v", err))
}
}()
@ -223,6 +226,7 @@ func (d *deps) runVMRun(ctx context.Context, socketPath string, cfg model.Daemon
fromRef = repo.fromRef
}
if !repo.includeUntracked {
progress.clear()
d.noteUntrackedSkipped(ctx, stderr, repo.sourcePath)
}
prepared, err := d.vmWorkspacePrepare(ctx, socketPath, api.VMWorkspacePrepareParams{
@ -246,13 +250,14 @@ func (d *deps) runVMRun(ctx context.Context, socketPath string, cfg model.Daemon
return fmt.Errorf("vm %q is running but guest ssh is unavailable: %w", vmRef, err)
}
if err := d.startVMRunToolingHarness(ctx, client, prepared.Workspace.RepoRoot, prepared.Workspace.RepoName, progress, detach, stderr); err != nil {
progress.clear()
printVMRunWarning(stderr, fmt.Sprintf("guest tooling bootstrap start failed: %v", err))
}
_ = client.Close()
}
}
if detach {
progress.render(fmt.Sprintf("vm %s running; reconnect with: banger vm ssh %s", vmRef, vmRef))
progress.commitLine(fmt.Sprintf("vm %s running; reconnect with: banger vm ssh %s", vmRef, vmRef))
return nil
}
sshArgs, err := sshCommandArgs(cfg, vm.Runtime.GuestIP, command)
@ -261,6 +266,7 @@ func (d *deps) runVMRun(ctx context.Context, socketPath string, cfg model.Daemon
}
if len(command) > 0 {
progress.render("running command in guest")
progress.clear()
if err := d.sshExec(ctx, stdin, stdout, stderr, sshArgs); err != nil {
var exitErr *exec.ExitError
if errors.As(err, &exitErr) {
@ -271,6 +277,7 @@ func (d *deps) runVMRun(ctx context.Context, socketPath string, cfg model.Daemon
return nil
}
progress.render("attaching to guest")
progress.clear()
return d.runSSHSession(ctx, socketPath, vmRef, stdin, stdout, stderr, sshArgs, removeOnExit)
}
@ -442,13 +449,24 @@ func formatVMRunStepError(action string, err error, log string) error {
type vmRunProgressRenderer struct {
out io.Writer
enabled bool
inline bool
active bool
lastLine string
}
func newVMRunProgressRenderer(out io.Writer) *vmRunProgressRenderer {
// newVMRunProgressRenderer wires up progress for `vm run`. Unlike the
// vm_create renderer, this one emits in line mode even on non-TTY
// writers (covers tests and piped output that the existing tooling
// already parses); inline mode kicks in only when stderr is a TTY,
// verbose is unset, and BANGER_NO_PROGRESS is unset.
func newVMRunProgressRenderer(out io.Writer, verbose bool) *vmRunProgressRenderer {
if out == nil {
return &vmRunProgressRenderer{}
}
return &vmRunProgressRenderer{
out: out,
enabled: out != nil,
enabled: true,
inline: writerSupportsProgress(out) && !verbose && !progressDisabledByEnv(),
}
}
@ -461,6 +479,47 @@ func (r *vmRunProgressRenderer) render(detail string) {
return
}
r.lastLine = line
if r.inline {
_, _ = fmt.Fprint(r.out, "\r\x1b[K", line)
r.active = true
return
}
_, _ = fmt.Fprintln(r.out, line)
}
// clear erases the live inline line so the caller can write a clean
// terminating message (warning, ssh attach, command output). No-op
// outside inline mode.
func (r *vmRunProgressRenderer) clear() {
if r == nil || !r.enabled || !r.inline || !r.active {
return
}
_, _ = fmt.Fprint(r.out, "\r\x1b[K")
r.active = false
r.lastLine = ""
}
// commitLine prints detail as a final, persistent line. In inline
// mode it overwrites the live status; in line mode it just appends.
// Used for terminal messages like the --detach hand-off summary.
func (r *vmRunProgressRenderer) commitLine(detail string) {
if r == nil || !r.enabled {
return
}
line := formatVMRunProgress(detail)
if line == "" {
return
}
if r.inline {
_, _ = fmt.Fprint(r.out, "\r\x1b[K", line, "\n")
r.active = false
r.lastLine = ""
return
}
if line == r.lastLine {
return
}
r.lastLine = line
_, _ = fmt.Fprintln(r.out, line)
}

View file

@ -124,7 +124,7 @@ func TestRunVMRunRefusesBootstrapWithoutNAT(t *testing.T) {
api.VMCreateParams{Name: "devbox", NATEnabled: false},
&repo,
nil,
false, false, false,
false, false, false, false,
)
if err == nil || !strings.Contains(err.Error(), "tooling bootstrap requires --nat") {
t.Fatalf("runVMRun = %v, want NAT precondition refusal", err)
@ -155,7 +155,7 @@ func TestRunVMRunBootstrapPreconditionRespectsNoBootstrap(t *testing.T) {
api.VMCreateParams{Name: "devbox", NATEnabled: false},
&repo,
nil,
false, false, true, // skipBootstrap = true
false, false, true, false, // skipBootstrap = true
)
if err != nil {
t.Fatalf("runVMRun: %v", err)
@ -186,7 +186,7 @@ func TestRunVMRunBootstrapPreconditionPassesWithoutMiseFiles(t *testing.T) {
api.VMCreateParams{Name: "devbox", NATEnabled: false},
&repo,
nil,
false, false, false,
false, false, false, false,
)
if err != nil {
t.Fatalf("runVMRun: %v", err)
@ -219,7 +219,7 @@ func TestRunVMRunDetachSkipsSshAttach(t *testing.T) {
api.VMCreateParams{Name: "devbox"},
nil, // bare mode
nil, // no command
false, true, false, // detach = true
false, true, false, false, // detach = true
)
if err != nil {
t.Fatalf("runVMRun: %v", err)
@ -257,7 +257,7 @@ func TestRunVMRunDetachUsesSyncBootstrapPath(t *testing.T) {
api.VMCreateParams{Name: "devbox", NATEnabled: true},
&repo,
nil,
false, true, false, // detach = true
false, true, false, false, // detach = true
)
if err != nil {
t.Fatalf("runVMRun: %v", err)