From 3a5f4cd40ddaa0da36bca4f4f7b6e721e3fe0ad6 Mon Sep 17 00:00:00 2001 From: Thales Maciel Date: Sun, 19 Apr 2026 17:01:26 -0300 Subject: [PATCH] cli: delete vm run's dead import path + duplicated git inspection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The CLI carried a full second copy of the workspace import implementation that `vm run` never actually used: - importVMRunRepoToGuest (no callers — the live flow calls the daemon's PrepareVMWorkspace RPC instead) - prepareVMRunRepoCopy, vmRunCheckoutCommit, vmRunCheckoutScript, gitFileURL, runHostCommand (all reachable only from the dead importVMRunRepoToGuest) Plus a duplicated repo-inspection surface that shadowed the daemon's: - inspectVMRunRepo ran every git query the daemon re-ran during workspace.prepare (HEAD, branch, identity, origin, overlay list) - gitOutput / gitTrimmedOutput / gitResolvedConfigValue / parseNullSeparatedOutput / listSubmodules / listOverlayPaths / resolveVMRunSourcePath — all identical to the exported workspace.* versions - vmRunRepoSpec — same fields as workspace.RepoSpec Replaced with a single minimal preflight: func vmRunPreflightRepo(ctx, rawPath) (absPath, err error) The preflight only checks what the user can fix locally before banger creates a VM (path exists, sits in a non-bare git repo, no submodules). The daemon's workspace.prepare RPC does the full inspection — and returns RepoRoot + RepoName in the response, which the CLI now threads into the tooling harness instead of computing them a second time. Signature changes: runVMRun(ctx, ..., *vmRunRepo, ...) // was: *vmRunRepoSpec startVMRunToolingHarness(ctx, client, repoRoot, repoName, progress) // was: (ctx, client, spec, progress) vmRunToolingHarnessScript(plan) // was: (spec, plan) vmRunToolingHarnessLaunchScript(repoName) // was: (spec) Tests: the CLI-side git-inspection tests are replaced by a single TestVMRunPreflightRejectsSubmodules that exercises the preflight. Everything else (tooling harness script, progress renderer, SSH args, runVMRun flows) keeps working. The shallow-copy / checkout-script tests are gone — that code now lives only in internal/daemon/workspace and is tested there. Also fixed a latent bug the refactor exposed: vm run's --from flag defaults to "HEAD", which the daemon reads as "from without branch" and rejects. CLI now scrubs fromRef when branchName is empty. Live verified: `banger vm run --name X . -- cmd` boots, workspace materialises at /root/repo with matching HEAD, exit code propagates. --- internal/cli/banger.go | 410 +++++++++------------------------------ internal/cli/cli_test.go | 200 +++---------------- 2 files changed, 112 insertions(+), 498 deletions(-) diff --git a/internal/cli/banger.go b/internal/cli/banger.go index 8cbeab1..ac3d68a 100644 --- a/internal/cli/banger.go +++ b/internal/cli/banger.go @@ -13,7 +13,6 @@ import ( "io" "io/fs" "net" - "net/url" "os" "os/exec" "path/filepath" @@ -28,6 +27,7 @@ import ( "banger/internal/buildinfo" "banger/internal/config" "banger/internal/daemon" + "banger/internal/daemon/workspace" "banger/internal/guest" "banger/internal/hostnat" "banger/internal/imagecat" @@ -138,7 +138,6 @@ var ( knownHosts, _ := bangerKnownHostsPath() return guest.Dial(ctx, address, privateKeyPath, knownHosts) } - prepareVMRunRepoCopyFunc = prepareVMRunRepoCopy buildVMRunToolingPlanFunc = toolingplan.Build cwdFunc = os.Getwd ) @@ -151,23 +150,17 @@ type vmRunGuestClient interface { StreamTarEntries(ctx context.Context, sourceDir string, entries []string, remoteCommand string, logWriter io.Writer) error } -type vmRunRepoSpec struct { - SourcePath string - RepoRoot string - RepoName string - HeadCommit string - CurrentBranch string - BranchName string - FromRef string - BaseCommit string - OriginURL string - GitUserName string - GitUserEmail string - OverlayPaths []string +// vmRunRepo is the CLI-local view of the workspace argument to +// `vm run`: an absolute source path that passed preflight, plus the +// two branch flags. Everything else the flow needs (RepoRoot, +// RepoName, HEAD commit, etc.) comes back from the workspace.prepare +// RPC, which does the full git inspection daemon-side. +type vmRunRepo struct { + sourcePath string + branchName string + fromRef string } -const vmRunShallowFetchDepth = 10 - const vmRunToolingInstallTimeoutSeconds = 120 // vmRunSSHTimeout bounds how long `vm run` waits for guest ssh after @@ -791,13 +784,13 @@ Three modes: return errors.New("--branch requires a path argument") } - var specPtr *vmRunRepoSpec + var repoPtr *vmRunRepo if sourcePath != "" { - spec, err := inspectVMRunRepo(cmd.Context(), sourcePath, branchName, fromRef) + resolved, err := vmRunPreflightRepo(cmd.Context(), sourcePath) if err != nil { return err } - specPtr = &spec + repoPtr = &vmRunRepo{sourcePath: resolved, branchName: branchName, fromRef: fromRef} } layout, err := paths.Resolve() @@ -808,7 +801,7 @@ Three modes: if err != nil { return err } - if specPtr != nil { + if repoPtr != nil { if err := validateVMRunPrereqs(cfg); err != nil { return err } @@ -828,7 +821,7 @@ Three modes: if err != nil { return err } - return runVMRun(cmd.Context(), layout.SocketPath, cfg, cmd.InOrStdin(), cmd.OutOrStdout(), cmd.ErrOrStderr(), params, specPtr, commandArgs, removeOnExit) + return runVMRun(cmd.Context(), layout.SocketPath, cfg, cmd.InOrStdin(), cmd.OutOrStdout(), cmd.ErrOrStderr(), params, repoPtr, commandArgs, removeOnExit) }, } cmd.Flags().StringVar(&name, "name", "", "vm name") @@ -1280,7 +1273,14 @@ func newVMWorkspacePrepareCommand() *cobra.Command { if len(args) > 1 { sourcePath = args[1] } - resolvedPath, err := resolveVMRunSourcePath(sourcePath) + if strings.TrimSpace(sourcePath) == "" { + wd, err := cwdFunc() + if err != nil { + return err + } + sourcePath = wd + } + resolvedPath, err := workspace.ResolveSourcePath(sourcePath) if err != nil { return err } @@ -2732,86 +2732,20 @@ func validateVMRunPrereqs(cfg model.DaemonConfig) error { return checks.Err("vm run preflight failed") } -func inspectVMRunRepo(ctx context.Context, rawPath, branchName, fromRef string) (vmRunRepoSpec, error) { - sourcePath, err := resolveVMRunSourcePath(rawPath) - if err != nil { - return vmRunRepoSpec{}, err - } - - repoRoot, err := gitTrimmedOutput(ctx, sourcePath, "rev-parse", "--show-toplevel") - if err != nil { - return vmRunRepoSpec{}, fmt.Errorf("%s is not inside a git repository", sourcePath) - } - isBare, err := gitTrimmedOutput(ctx, repoRoot, "rev-parse", "--is-bare-repository") - if err != nil { - return vmRunRepoSpec{}, fmt.Errorf("inspect git repository %s: %w", repoRoot, err) - } - if isBare == "true" { - return vmRunRepoSpec{}, fmt.Errorf("vm run requires a non-bare git repository: %s", repoRoot) - } - if err := ensureVMRunRepoHasNoSubmodules(ctx, repoRoot); err != nil { - return vmRunRepoSpec{}, err - } - - headCommit, err := gitTrimmedOutput(ctx, repoRoot, "rev-parse", "HEAD^{commit}") - if err != nil { - return vmRunRepoSpec{}, fmt.Errorf("git repository %s must have at least one commit", repoRoot) - } - currentBranch, err := gitTrimmedOutput(ctx, repoRoot, "branch", "--show-current") - if err != nil { - return vmRunRepoSpec{}, fmt.Errorf("resolve current branch for %s: %w", repoRoot, err) - } - - baseCommit := headCommit - resolvedFromRef := "" - branchName = strings.TrimSpace(branchName) - if branchName != "" { - fromRef = strings.TrimSpace(fromRef) - if fromRef == "" { - return vmRunRepoSpec{}, errors.New("--from cannot be empty") - } - resolvedFromRef = fromRef - baseCommit, err = gitTrimmedOutput(ctx, repoRoot, "rev-parse", fromRef+"^{commit}") - if err != nil { - return vmRunRepoSpec{}, fmt.Errorf("resolve --from %q: %w", fromRef, err) - } - } - - gitUserName, err := gitResolvedConfigValue(ctx, repoRoot, "user.name") - if err != nil { - return vmRunRepoSpec{}, fmt.Errorf("resolve git user.name for %s: %w", repoRoot, err) - } - gitUserEmail, err := gitResolvedConfigValue(ctx, repoRoot, "user.email") - if err != nil { - return vmRunRepoSpec{}, fmt.Errorf("resolve git user.email for %s: %w", repoRoot, err) - } - originURL, err := gitResolvedConfigValue(ctx, repoRoot, "remote.origin.url") - if err != nil { - return vmRunRepoSpec{}, fmt.Errorf("resolve origin url for %s: %w", repoRoot, err) - } - - overlayPaths, err := listVMRunOverlayPaths(ctx, repoRoot) - if err != nil { - return vmRunRepoSpec{}, err - } - - return vmRunRepoSpec{ - SourcePath: sourcePath, - RepoRoot: repoRoot, - RepoName: filepath.Base(repoRoot), - HeadCommit: headCommit, - CurrentBranch: currentBranch, - BranchName: branchName, - FromRef: resolvedFromRef, - BaseCommit: baseCommit, - OriginURL: originURL, - GitUserName: gitUserName, - GitUserEmail: gitUserEmail, - OverlayPaths: overlayPaths, - }, nil -} - -func resolveVMRunSourcePath(rawPath string) (string, error) { +// vmRunPreflightRepo validates a vm run workspace path BEFORE the VM +// is created, so bad paths fail fast instead of leaving the user +// with an orphaned VM. The check is intentionally minimal: the +// daemon's PrepareVMWorkspace does a full git inspection (branch, +// HEAD, identity, overlay) and returns everything the tooling +// harness needs, so duplicating the heavy lifting here just doubles +// the I/O. We only enforce what the user can fix locally before +// banger commits to creating a VM: +// +// - the path exists and is a directory, +// - it sits inside a non-bare git repository, +// - the repository has no submodules (unsupported in the shallow +// overlay mode vm run uses). +func vmRunPreflightRepo(ctx context.Context, rawPath string) (string, error) { if strings.TrimSpace(rawPath) == "" { wd, err := cwdFunc() if err != nil { @@ -2819,104 +2753,29 @@ func resolveVMRunSourcePath(rawPath string) (string, error) { } rawPath = wd } - absPath, err := filepath.Abs(rawPath) + sourcePath, err := workspace.ResolveSourcePath(rawPath) if err != nil { return "", err } - info, err := os.Stat(absPath) + repoRoot, err := workspace.GitTrimmedOutput(ctx, sourcePath, "rev-parse", "--show-toplevel") + if err != nil { + return "", fmt.Errorf("%s is not inside a git repository", sourcePath) + } + isBare, err := workspace.GitTrimmedOutput(ctx, repoRoot, "rev-parse", "--is-bare-repository") + if err != nil { + return "", fmt.Errorf("inspect git repository %s: %w", repoRoot, err) + } + if isBare == "true" { + return "", fmt.Errorf("vm run requires a non-bare git repository: %s", repoRoot) + } + submodules, err := workspace.ListSubmodules(ctx, repoRoot) if err != nil { return "", err } - if !info.IsDir() { - return "", fmt.Errorf("%s is not a directory", absPath) + if len(submodules) > 0 { + return "", fmt.Errorf("vm run does not support git submodules in %s (%s); use `vm create` + `vm workspace prepare --mode full_copy`", repoRoot, strings.Join(submodules, ", ")) } - return absPath, nil -} - -func ensureVMRunRepoHasNoSubmodules(ctx context.Context, repoRoot string) error { - output, err := gitOutput(ctx, repoRoot, "ls-files", "--stage", "-z") - if err != nil { - return fmt.Errorf("inspect git index for %s: %w", repoRoot, err) - } - for _, record := range parseNullSeparatedOutput(output) { - if strings.HasPrefix(record, "160000 ") { - return fmt.Errorf("vm run does not yet support git submodules: %s", repoRoot) - } - } - return nil -} - -func listVMRunOverlayPaths(ctx context.Context, repoRoot string) ([]string, error) { - trackedOutput, err := gitOutput(ctx, repoRoot, "ls-files", "-z") - if err != nil { - return nil, fmt.Errorf("list tracked files for %s: %w", repoRoot, err) - } - untrackedOutput, err := gitOutput(ctx, repoRoot, "ls-files", "--others", "--exclude-standard", "-z") - if err != nil { - return nil, fmt.Errorf("list untracked files for %s: %w", repoRoot, err) - } - - paths := make([]string, 0) - seen := make(map[string]struct{}) - for _, relPath := range parseNullSeparatedOutput(trackedOutput) { - if relPath == "" { - continue - } - if _, err := os.Lstat(filepath.Join(repoRoot, relPath)); err != nil { - if os.IsNotExist(err) { - continue - } - return nil, err - } - seen[relPath] = struct{}{} - paths = append(paths, relPath) - } - for _, relPath := range parseNullSeparatedOutput(untrackedOutput) { - if relPath == "" { - continue - } - if _, ok := seen[relPath]; ok { - continue - } - seen[relPath] = struct{}{} - paths = append(paths, relPath) - } - sort.Strings(paths) - return paths, nil -} - -func gitOutput(ctx context.Context, dir string, args ...string) ([]byte, error) { - fullArgs := make([]string, 0, len(args)+2) - if strings.TrimSpace(dir) != "" { - fullArgs = append(fullArgs, "-C", dir) - } - fullArgs = append(fullArgs, args...) - return hostCommandOutputFunc(ctx, "git", fullArgs...) -} - -func gitTrimmedOutput(ctx context.Context, dir string, args ...string) (string, error) { - output, err := gitOutput(ctx, dir, args...) - if err != nil { - return "", err - } - return strings.TrimSpace(string(output)), nil -} - -func gitResolvedConfigValue(ctx context.Context, dir, key string) (string, error) { - return gitTrimmedOutput(ctx, dir, "config", "--default", "", "--get", key) -} - -func parseNullSeparatedOutput(output []byte) []string { - chunks := bytes.Split(output, []byte{0}) - values := make([]string, 0, len(chunks)) - for _, chunk := range chunks { - value := strings.TrimSpace(string(chunk)) - if value == "" { - continue - } - values = append(values, value) - } - return values + return sourcePath, nil } // splitVMRunArgs partitions cobra positional args into the optional path @@ -2946,7 +2805,7 @@ func (e ExitCodeError) Error() string { return fmt.Sprintf("exit status %d", e.Code) } -func runVMRun(ctx context.Context, socketPath string, cfg model.DaemonConfig, stdin io.Reader, stdout, stderr io.Writer, params api.VMCreateParams, spec *vmRunRepoSpec, command []string, removeOnExit bool) error { +func runVMRun(ctx context.Context, socketPath string, cfg model.DaemonConfig, stdin io.Reader, stdout, stderr io.Writer, params api.VMCreateParams, repo *vmRunRepo, command []string, removeOnExit bool) error { progress := newVMRunProgressRenderer(stderr) vm, err := runVMCreate(ctx, socketPath, stderr, params) if err != nil { @@ -2997,24 +2856,37 @@ func runVMRun(ctx context.Context, socketPath string, cfg model.DaemonConfig, st } cancelSSH() shouldRemove = removeOnExit - if spec != nil { + if repo != nil { progress.render("preparing guest workspace") - if _, err := vmWorkspacePrepareFunc(ctx, socketPath, api.VMWorkspacePrepareParams{ + // --from is only meaningful paired with --branch; the daemon + // rejects "from without branch" outright. Our flag default is + // "HEAD" (useful only when --branch is set), so scrub it when + // branch is empty to avoid a false "workspace from requires + // branch" error. + fromRef := "" + if strings.TrimSpace(repo.branchName) != "" { + fromRef = repo.fromRef + } + prepared, err := vmWorkspacePrepareFunc(ctx, socketPath, api.VMWorkspacePrepareParams{ IDOrName: vmRef, - SourcePath: spec.SourcePath, + SourcePath: repo.sourcePath, GuestPath: vmRunGuestDir(), - Branch: spec.BranchName, - From: spec.FromRef, + Branch: repo.branchName, + From: fromRef, Mode: string(model.WorkspacePrepareModeShallowOverlay), - }); err != nil { + }) + if err != nil { return fmt.Errorf("vm %q is running but workspace prepare failed: %w", vmRef, err) } + // The prepare RPC already did the full git inspection on the + // daemon side; grab what the tooling harness needs from its + // result instead of re-inspecting here. if len(command) == 0 { client, err := guestDialFunc(ctx, sshAddress, cfg.SSHKeyPath) if err != nil { return fmt.Errorf("vm %q is running but guest ssh is unavailable: %w", vmRef, err) } - if err := startVMRunToolingHarness(ctx, client, *spec, progress); err != nil { + if err := startVMRunToolingHarness(ctx, client, prepared.Workspace.RepoRoot, prepared.Workspace.RepoName, progress); err != nil { printVMRunWarning(stderr, fmt.Sprintf("guest tooling bootstrap start failed: %v", err)) } _ = client.Close() @@ -3039,118 +2911,6 @@ func runVMRun(ctx context.Context, socketPath string, cfg model.DaemonConfig, st return runSSHSession(ctx, socketPath, vmRef, stdin, stdout, stderr, sshArgs, removeOnExit) } -func importVMRunRepoToGuest(ctx context.Context, client vmRunGuestClient, spec vmRunRepoSpec, progress *vmRunProgressRenderer) error { - if progress != nil { - progress.render("preparing shallow repo") - } - repoCopyDir, cleanup, err := prepareVMRunRepoCopyFunc(ctx, spec) - if err != nil { - return err - } - defer cleanup() - if progress != nil { - progress.render("copying repo metadata to guest") - } - var copyLog bytes.Buffer - remoteCommand := fmt.Sprintf("rm -rf %s && mkdir -p %s && tar -o -C %s --strip-components=1 -xf -", shellQuote(vmRunGuestDir()), shellQuote(vmRunGuestDir()), shellQuote(vmRunGuestDir())) - if err := client.StreamTar(ctx, repoCopyDir, remoteCommand, ©Log); err != nil { - return formatVMRunStepError("copy guest git metadata", err, copyLog.String()) - } - if progress != nil { - progress.render("preparing guest checkout") - } - var scriptLog bytes.Buffer - if err := client.RunScript(ctx, vmRunCheckoutScript(spec), &scriptLog); err != nil { - return formatVMRunStepError("prepare guest checkout", err, scriptLog.String()) - } - if progress != nil { - progress.render("overlaying host working tree") - } - var overlayLog bytes.Buffer - remoteCommand = fmt.Sprintf("tar -o -C %s --strip-components=1 -xf -", shellQuote(vmRunGuestDir())) - if err := client.StreamTarEntries(ctx, spec.RepoRoot, spec.OverlayPaths, remoteCommand, &overlayLog); err != nil { - return formatVMRunStepError("overlay host working tree", err, overlayLog.String()) - } - return nil -} - -func prepareVMRunRepoCopy(ctx context.Context, spec vmRunRepoSpec) (string, func(), error) { - tempRoot, err := os.MkdirTemp("", "banger-vm-run-*") - if err != nil { - return "", nil, err - } - cleanup := func() { - _ = os.RemoveAll(tempRoot) - } - repoCopyDir := filepath.Join(tempRoot, spec.RepoName) - cloneArgs := []string{"clone", "--no-checkout", "--depth", fmt.Sprintf("%d", vmRunShallowFetchDepth)} - if strings.TrimSpace(spec.CurrentBranch) != "" { - cloneArgs = append(cloneArgs, "--single-branch", "--branch", spec.CurrentBranch) - } - cloneArgs = append(cloneArgs, gitFileURL(spec.RepoRoot), repoCopyDir) - if err := runHostCommand(ctx, "git", cloneArgs...); err != nil { - cleanup() - return "", nil, fmt.Errorf("clone shallow repo copy: %w", err) - } - checkoutCommit := vmRunCheckoutCommit(spec) - if err := runHostCommand(ctx, "git", "-C", repoCopyDir, "cat-file", "-e", checkoutCommit+"^{commit}"); err != nil { - if err := runHostCommand(ctx, "git", "-C", repoCopyDir, "fetch", "--depth", fmt.Sprintf("%d", vmRunShallowFetchDepth), gitFileURL(spec.RepoRoot), checkoutCommit); err != nil { - cleanup() - return "", nil, fmt.Errorf("fetch shallow repo commit %s: %w", checkoutCommit, err) - } - } - if strings.TrimSpace(spec.OriginURL) != "" { - if err := runHostCommand(ctx, "git", "-C", repoCopyDir, "remote", "set-url", "origin", spec.OriginURL); err != nil { - cleanup() - return "", nil, fmt.Errorf("set origin remote: %w", err) - } - } else { - if err := runHostCommand(ctx, "git", "-C", repoCopyDir, "remote", "remove", "origin"); err != nil { - cleanup() - return "", nil, fmt.Errorf("remove placeholder origin remote: %w", err) - } - } - return repoCopyDir, cleanup, nil -} - -func vmRunCheckoutCommit(spec vmRunRepoSpec) string { - if strings.TrimSpace(spec.BranchName) != "" { - return spec.BaseCommit - } - return spec.HeadCommit -} - -func gitFileURL(path string) string { - return (&url.URL{Scheme: "file", Path: filepath.ToSlash(path)}).String() -} - -func runHostCommand(ctx context.Context, name string, args ...string) error { - _, err := hostCommandOutputFunc(ctx, name, args...) - return err -} - -func vmRunCheckoutScript(spec vmRunRepoSpec) string { - guestDir := vmRunGuestDir() - var script strings.Builder - script.WriteString("set -euo pipefail\n") - fmt.Fprintf(&script, "DIR=%s\n", shellQuote(guestDir)) - script.WriteString("git config --global --add safe.directory \"$DIR\"\n") - switch { - case strings.TrimSpace(spec.BranchName) != "": - fmt.Fprintf(&script, "git -C \"$DIR\" checkout -B %s %s\n", shellQuote(spec.BranchName), shellQuote(spec.BaseCommit)) - case strings.TrimSpace(spec.CurrentBranch) != "": - fmt.Fprintf(&script, "git -C \"$DIR\" checkout -B %s %s\n", shellQuote(spec.CurrentBranch), shellQuote(spec.HeadCommit)) - default: - fmt.Fprintf(&script, "git -C \"$DIR\" checkout --detach %s\n", shellQuote(spec.HeadCommit)) - } - script.WriteString("find \"$DIR\" -mindepth 1 -maxdepth 1 ! -name .git -exec rm -rf {} +\n") - if strings.TrimSpace(spec.GitUserName) != "" && strings.TrimSpace(spec.GitUserEmail) != "" { - fmt.Fprintf(&script, "git -C \"$DIR\" config user.name %s\n", shellQuote(spec.GitUserName)) - fmt.Fprintf(&script, "git -C \"$DIR\" config user.email %s\n", shellQuote(spec.GitUserEmail)) - } - return script.String() -} - func vmRunGuestDir() string { return "/root/repo" } @@ -3164,26 +2924,30 @@ func vmRunToolingHarnessLogPath(repoName string) string { return filepath.ToSlash(filepath.Join("/root/.cache/banger", "vm-run-tooling-"+repoName+".log")) } -func startVMRunToolingHarness(ctx context.Context, client vmRunGuestClient, spec vmRunRepoSpec, progress *vmRunProgressRenderer) error { +// startVMRunToolingHarness uploads + launches the mise bootstrap +// script inside the guest. repoRoot / repoName both come from the +// daemon's workspace.prepare RPC response — the CLI no longer does +// its own git inspection. +func startVMRunToolingHarness(ctx context.Context, client vmRunGuestClient, repoRoot, repoName string, progress *vmRunProgressRenderer) error { if progress != nil { progress.render("starting guest tooling bootstrap") } - plan := buildVMRunToolingPlanFunc(ctx, spec.RepoRoot) + plan := buildVMRunToolingPlanFunc(ctx, repoRoot) var uploadLog bytes.Buffer - if err := client.UploadFile(ctx, vmRunToolingHarnessPath(spec.RepoName), 0o755, []byte(vmRunToolingHarnessScript(spec, plan)), &uploadLog); err != nil { + if err := client.UploadFile(ctx, vmRunToolingHarnessPath(repoName), 0o755, []byte(vmRunToolingHarnessScript(plan)), &uploadLog); err != nil { return formatVMRunStepError("upload guest tooling bootstrap", err, uploadLog.String()) } var launchLog bytes.Buffer - if err := client.RunScript(ctx, vmRunToolingHarnessLaunchScript(spec), &launchLog); err != nil { + if err := client.RunScript(ctx, vmRunToolingHarnessLaunchScript(repoName), &launchLog); err != nil { return formatVMRunStepError("launch guest tooling bootstrap", err, launchLog.String()) } if progress != nil { - progress.render("guest tooling log: " + vmRunToolingHarnessLogPath(spec.RepoName)) + progress.render("guest tooling log: " + vmRunToolingHarnessLogPath(repoName)) } return nil } -func vmRunToolingHarnessScript(spec vmRunRepoSpec, plan toolingplan.Plan) string { +func vmRunToolingHarnessScript(plan toolingplan.Plan) string { var script strings.Builder script.WriteString("set -uo pipefail\n") fmt.Fprintf(&script, "DIR=%s\n", shellQuote(vmRunGuestDir())) @@ -3260,11 +3024,11 @@ func vmRunToolingHarnessScript(spec vmRunRepoSpec, plan toolingplan.Plan) string return script.String() } -func vmRunToolingHarnessLaunchScript(spec vmRunRepoSpec) string { +func vmRunToolingHarnessLaunchScript(repoName string) string { var script strings.Builder script.WriteString("set -euo pipefail\n") - fmt.Fprintf(&script, "HELPER=%s\n", shellQuote(vmRunToolingHarnessPath(spec.RepoName))) - fmt.Fprintf(&script, "LOG=%s\n", shellQuote(vmRunToolingHarnessLogPath(spec.RepoName))) + fmt.Fprintf(&script, "HELPER=%s\n", shellQuote(vmRunToolingHarnessPath(repoName))) + fmt.Fprintf(&script, "LOG=%s\n", shellQuote(vmRunToolingHarnessLogPath(repoName))) script.WriteString("mkdir -p \"$(dirname \"$LOG\")\"\n") script.WriteString("nohup bash \"$HELPER\" >\"$LOG\" 2>&1