Bootstrap vm run tooling before attach
Speed up first use of repo backed VMs by bootstrapping obvious tools before the best effort LLM harness runs. Add a host side tooling plan for pinned Go, Node, Python, and Rust versions, summarize that plan in the uploaded prompt, and run repo mise install plus guest global mise use -g --pin steps before the bounded opencode inspection. Keep the harness non fatal, prefer host opencode attach when the client supports it, fall back to guest opencode over SSH for older clients, and cover the new flow with CLI plus planner tests. Validation: - go test ./internal/cli ./internal/toolingplan - GOCACHE=/tmp/banger-gocache go test ./... - make build
This commit is contained in:
parent
1e967140c3
commit
4813e844e2
10 changed files with 1126 additions and 13 deletions
|
|
@ -18,6 +18,7 @@ import (
|
|||
"banger/internal/buildinfo"
|
||||
"banger/internal/model"
|
||||
"banger/internal/system"
|
||||
"banger/internal/toolingplan"
|
||||
)
|
||||
|
||||
func TestNewBangerCommandHasExpectedSubcommands(t *testing.T) {
|
||||
|
|
@ -1050,7 +1051,9 @@ func TestRunVMRunCreatesImportsAndAttaches(t *testing.T) {
|
|||
origWaitForSSH := guestWaitForSSHFunc
|
||||
origGuestDial := guestDialFunc
|
||||
origPrepareVMRunRepoCopy := prepareVMRunRepoCopyFunc
|
||||
origBuildVMRunToolingPlan := buildVMRunToolingPlanFunc
|
||||
origOpencodeExec := opencodeExecFunc
|
||||
origHostOpencodeAttachSupported := hostOpencodeAttachSupportedFunc
|
||||
t.Cleanup(func() {
|
||||
vmCreateBeginFunc = origBegin
|
||||
vmCreateStatusFunc = origStatus
|
||||
|
|
@ -1058,7 +1061,9 @@ func TestRunVMRunCreatesImportsAndAttaches(t *testing.T) {
|
|||
guestWaitForSSHFunc = origWaitForSSH
|
||||
guestDialFunc = origGuestDial
|
||||
prepareVMRunRepoCopyFunc = origPrepareVMRunRepoCopy
|
||||
buildVMRunToolingPlanFunc = origBuildVMRunToolingPlan
|
||||
opencodeExecFunc = origOpencodeExec
|
||||
hostOpencodeAttachSupportedFunc = origHostOpencodeAttachSupported
|
||||
})
|
||||
|
||||
vm := model.VMRecord{
|
||||
|
|
@ -1113,6 +1118,15 @@ func TestRunVMRunCreatesImportsAndAttaches(t *testing.T) {
|
|||
}
|
||||
return repoCopyDir, func() {}, nil
|
||||
}
|
||||
hostOpencodeAttachSupportedFunc = func(context.Context) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
buildVMRunToolingPlanFunc = func(context.Context, string) toolingplan.Plan {
|
||||
return toolingplan.Plan{
|
||||
Steps: []toolingplan.InstallStep{{Tool: "go", Version: "1.25.0", Source: "go.mod"}},
|
||||
Skips: []toolingplan.SkipNote{{Target: "python", Reason: "no .python-version"}},
|
||||
}
|
||||
}
|
||||
var attachArgs []string
|
||||
opencodeExecFunc = func(ctx context.Context, stdin io.Reader, stdout, stderr io.Writer, args []string) error {
|
||||
attachArgs = append([]string(nil), args...)
|
||||
|
|
@ -1165,6 +1179,54 @@ func TestRunVMRunCreatesImportsAndAttaches(t *testing.T) {
|
|||
if fakeClient.tarCommand != "rm -rf '/root/repo' && mkdir -p '/root/repo' && tar -o -C '/root/repo' --strip-components=1 -xf -" {
|
||||
t.Fatalf("tarCommand = %q", fakeClient.tarCommand)
|
||||
}
|
||||
if len(fakeClient.uploads) != 2 {
|
||||
t.Fatalf("uploads = %d, want 2", len(fakeClient.uploads))
|
||||
}
|
||||
if fakeClient.uploads[0].path != vmRunToolingHarnessPromptPath("repo") {
|
||||
t.Fatalf("prompt upload path = %q, want %q", fakeClient.uploads[0].path, vmRunToolingHarnessPromptPath("repo"))
|
||||
}
|
||||
if fakeClient.uploads[0].mode != 0o644 {
|
||||
t.Fatalf("prompt upload mode = %v, want 0644", fakeClient.uploads[0].mode)
|
||||
}
|
||||
if !strings.Contains(string(fakeClient.uploads[0].data), `Do not edit repository files.`) {
|
||||
t.Fatalf("prompt upload data = %q, want prompt body", string(fakeClient.uploads[0].data))
|
||||
}
|
||||
if !strings.Contains(string(fakeClient.uploads[0].data), `Planned deterministic install: go@1.25.0 from go.mod`) {
|
||||
t.Fatalf("prompt upload data = %q, want deterministic install summary", string(fakeClient.uploads[0].data))
|
||||
}
|
||||
if !strings.Contains(string(fakeClient.uploads[0].data), `Deterministic skip: python (no .python-version)`) {
|
||||
t.Fatalf("prompt upload data = %q, want deterministic skip summary", string(fakeClient.uploads[0].data))
|
||||
}
|
||||
if fakeClient.uploadPath != vmRunToolingHarnessPath("repo") {
|
||||
t.Fatalf("uploadPath = %q, want %q", fakeClient.uploadPath, vmRunToolingHarnessPath("repo"))
|
||||
}
|
||||
if fakeClient.uploadMode != 0o755 {
|
||||
t.Fatalf("uploadMode = %v, want 0755", fakeClient.uploadMode)
|
||||
}
|
||||
if !strings.Contains(string(fakeClient.uploadData), `run_best_effort "$MISE_BIN" install`) {
|
||||
t.Fatalf("uploadData = %q, want mise install best-effort step", string(fakeClient.uploadData))
|
||||
}
|
||||
if !strings.Contains(string(fakeClient.uploadData), fmt.Sprintf(`INSTALL_TIMEOUT_SECS=%d`, vmRunToolingInstallTimeoutSeconds)) {
|
||||
t.Fatalf("uploadData = %q, want deterministic install timeout", string(fakeClient.uploadData))
|
||||
}
|
||||
if !strings.Contains(string(fakeClient.uploadData), `deterministic install: go@1.25.0 (go.mod)`) {
|
||||
t.Fatalf("uploadData = %q, want deterministic install log", string(fakeClient.uploadData))
|
||||
}
|
||||
if !strings.Contains(string(fakeClient.uploadData), `run_bounded_best_effort "$INSTALL_TIMEOUT_SECS" "$MISE_BIN" use -g --pin 'go@1.25.0'`) {
|
||||
t.Fatalf("uploadData = %q, want deterministic go install step", string(fakeClient.uploadData))
|
||||
}
|
||||
if !strings.Contains(string(fakeClient.uploadData), `deterministic skip: python (no .python-version)`) {
|
||||
t.Fatalf("uploadData = %q, want deterministic skip log", string(fakeClient.uploadData))
|
||||
}
|
||||
if !strings.Contains(string(fakeClient.uploadData), `run_best_effort "$MISE_BIN" reshim`) {
|
||||
t.Fatalf("uploadData = %q, want deterministic reshim step", string(fakeClient.uploadData))
|
||||
}
|
||||
if !strings.Contains(fakeClient.launchScript, `nohup bash "$HELPER" >"$LOG" 2>&1 </dev/null &`) {
|
||||
t.Fatalf("launchScript = %q, want nohup launcher", fakeClient.launchScript)
|
||||
}
|
||||
if !strings.Contains(fakeClient.launchScript, vmRunToolingHarnessLogPath("repo")) {
|
||||
t.Fatalf("launchScript = %q, want tooling harness log path", fakeClient.launchScript)
|
||||
}
|
||||
if !strings.Contains(fakeClient.script, `git -C "$DIR" checkout -B 'feature' 'cafebabe'`) {
|
||||
t.Fatalf("script = %q, want guest branch checkout", fakeClient.script)
|
||||
}
|
||||
|
|
@ -1206,6 +1268,7 @@ func TestVMRunPrintsPostCreateProgress(t *testing.T) {
|
|||
origGuestDial := guestDialFunc
|
||||
origPrepareVMRunRepoCopy := prepareVMRunRepoCopyFunc
|
||||
origOpencodeExec := opencodeExecFunc
|
||||
origHostOpencodeAttachSupported := hostOpencodeAttachSupportedFunc
|
||||
t.Cleanup(func() {
|
||||
vmCreateBeginFunc = origBegin
|
||||
vmCreateStatusFunc = origStatus
|
||||
|
|
@ -1214,6 +1277,7 @@ func TestVMRunPrintsPostCreateProgress(t *testing.T) {
|
|||
guestDialFunc = origGuestDial
|
||||
prepareVMRunRepoCopyFunc = origPrepareVMRunRepoCopy
|
||||
opencodeExecFunc = origOpencodeExec
|
||||
hostOpencodeAttachSupportedFunc = origHostOpencodeAttachSupported
|
||||
})
|
||||
|
||||
vm := model.VMRecord{
|
||||
|
|
@ -1253,6 +1317,9 @@ func TestVMRunPrintsPostCreateProgress(t *testing.T) {
|
|||
prepareVMRunRepoCopyFunc = func(ctx context.Context, spec vmRunRepoSpec) (string, func(), error) {
|
||||
return t.TempDir(), func() {}, nil
|
||||
}
|
||||
hostOpencodeAttachSupportedFunc = func(context.Context) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
opencodeExecFunc = func(ctx context.Context, stdin io.Reader, stdout, stderr io.Writer, args []string) error {
|
||||
return nil
|
||||
}
|
||||
|
|
@ -1279,6 +1346,8 @@ func TestVMRunPrintsPostCreateProgress(t *testing.T) {
|
|||
"[vm run] copying repo metadata to guest",
|
||||
"[vm run] preparing guest checkout",
|
||||
"[vm run] overlaying host working tree",
|
||||
"[vm run] starting tooling harness",
|
||||
"[vm run] tooling harness log: /root/.cache/banger/vm-run-tooling-repo.log",
|
||||
"[vm run] attaching opencode",
|
||||
} {
|
||||
if !strings.Contains(output, want) {
|
||||
|
|
@ -1287,6 +1356,219 @@ func TestVMRunPrintsPostCreateProgress(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestRunVMRunWarnsWhenToolingHarnessStartFails(t *testing.T) {
|
||||
origBegin := vmCreateBeginFunc
|
||||
origStatus := vmCreateStatusFunc
|
||||
origCancel := vmCreateCancelFunc
|
||||
origWaitForSSH := guestWaitForSSHFunc
|
||||
origGuestDial := guestDialFunc
|
||||
origPrepareVMRunRepoCopy := prepareVMRunRepoCopyFunc
|
||||
origOpencodeExec := opencodeExecFunc
|
||||
origHostOpencodeAttachSupported := hostOpencodeAttachSupportedFunc
|
||||
t.Cleanup(func() {
|
||||
vmCreateBeginFunc = origBegin
|
||||
vmCreateStatusFunc = origStatus
|
||||
vmCreateCancelFunc = origCancel
|
||||
guestWaitForSSHFunc = origWaitForSSH
|
||||
guestDialFunc = origGuestDial
|
||||
prepareVMRunRepoCopyFunc = origPrepareVMRunRepoCopy
|
||||
opencodeExecFunc = origOpencodeExec
|
||||
hostOpencodeAttachSupportedFunc = origHostOpencodeAttachSupported
|
||||
})
|
||||
|
||||
vm := model.VMRecord{
|
||||
ID: "vm-id",
|
||||
Name: "devbox",
|
||||
Runtime: model.VMRuntime{
|
||||
State: model.VMStateRunning,
|
||||
GuestIP: "172.16.0.2",
|
||||
},
|
||||
}
|
||||
vmCreateBeginFunc = func(context.Context, string, api.VMCreateParams) (api.VMCreateBeginResult, error) {
|
||||
return api.VMCreateBeginResult{Operation: api.VMCreateOperation{ID: "op-1", Stage: "ready", Detail: "vm is ready", Done: true, Success: true, VM: &vm}}, nil
|
||||
}
|
||||
vmCreateStatusFunc = func(context.Context, string, string) (api.VMCreateStatusResult, error) {
|
||||
t.Fatal("vmCreateStatusFunc should not be called")
|
||||
return api.VMCreateStatusResult{}, nil
|
||||
}
|
||||
vmCreateCancelFunc = func(context.Context, string, string) error {
|
||||
t.Fatal("vmCreateCancelFunc should not be called")
|
||||
return nil
|
||||
}
|
||||
guestWaitForSSHFunc = func(ctx context.Context, address, privateKeyPath string, interval time.Duration) error {
|
||||
return nil
|
||||
}
|
||||
fakeClient := &testVMRunGuestClient{launchErr: errors.New("launch failed")}
|
||||
guestDialFunc = func(ctx context.Context, address, privateKeyPath string) (vmRunGuestClient, error) {
|
||||
return fakeClient, nil
|
||||
}
|
||||
prepareVMRunRepoCopyFunc = func(ctx context.Context, spec vmRunRepoSpec) (string, func(), error) {
|
||||
return t.TempDir(), func() {}, nil
|
||||
}
|
||||
hostOpencodeAttachSupportedFunc = func(context.Context) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
attachCalled := false
|
||||
opencodeExecFunc = func(ctx context.Context, stdin io.Reader, stdout, stderr io.Writer, args []string) error {
|
||||
attachCalled = true
|
||||
return nil
|
||||
}
|
||||
|
||||
var stderr bytes.Buffer
|
||||
err := runVMRun(
|
||||
context.Background(),
|
||||
"/tmp/bangerd.sock",
|
||||
model.DaemonConfig{SSHKeyPath: "/tmp/id_ed25519"},
|
||||
strings.NewReader(""),
|
||||
&bytes.Buffer{},
|
||||
&stderr,
|
||||
api.VMCreateParams{Name: "devbox"},
|
||||
vmRunRepoSpec{RepoRoot: t.TempDir(), RepoName: "repo", HeadCommit: "deadbeef"},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("runVMRun: %v", err)
|
||||
}
|
||||
if !attachCalled {
|
||||
t.Fatal("opencode attach should still run when tooling harness launch fails")
|
||||
}
|
||||
if !strings.Contains(stderr.String(), "[vm run] warning: tooling harness start failed: launch tooling harness: launch failed") {
|
||||
t.Fatalf("stderr = %q, want tooling harness warning", stderr.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunVMRunFallsBackToGuestOpencodeWhenHostAttachUnsupported(t *testing.T) {
|
||||
repoRoot := t.TempDir()
|
||||
|
||||
origBegin := vmCreateBeginFunc
|
||||
origStatus := vmCreateStatusFunc
|
||||
origCancel := vmCreateCancelFunc
|
||||
origWaitForSSH := guestWaitForSSHFunc
|
||||
origGuestDial := guestDialFunc
|
||||
origPrepareVMRunRepoCopy := prepareVMRunRepoCopyFunc
|
||||
origOpencodeExec := opencodeExecFunc
|
||||
origHostOpencodeAttachSupported := hostOpencodeAttachSupportedFunc
|
||||
origSSHExec := sshExecFunc
|
||||
t.Cleanup(func() {
|
||||
vmCreateBeginFunc = origBegin
|
||||
vmCreateStatusFunc = origStatus
|
||||
vmCreateCancelFunc = origCancel
|
||||
guestWaitForSSHFunc = origWaitForSSH
|
||||
guestDialFunc = origGuestDial
|
||||
prepareVMRunRepoCopyFunc = origPrepareVMRunRepoCopy
|
||||
opencodeExecFunc = origOpencodeExec
|
||||
hostOpencodeAttachSupportedFunc = origHostOpencodeAttachSupported
|
||||
sshExecFunc = origSSHExec
|
||||
})
|
||||
|
||||
vm := model.VMRecord{
|
||||
ID: "vm-id",
|
||||
Name: "devbox",
|
||||
Runtime: model.VMRuntime{
|
||||
State: model.VMStateRunning,
|
||||
GuestIP: "172.16.0.2",
|
||||
},
|
||||
}
|
||||
vmCreateBeginFunc = func(context.Context, string, api.VMCreateParams) (api.VMCreateBeginResult, error) {
|
||||
return api.VMCreateBeginResult{Operation: api.VMCreateOperation{ID: "op-1", Stage: "ready", Detail: "vm is ready", Done: true, Success: true, VM: &vm}}, nil
|
||||
}
|
||||
vmCreateStatusFunc = func(context.Context, string, string) (api.VMCreateStatusResult, error) {
|
||||
t.Fatal("vmCreateStatusFunc should not be called")
|
||||
return api.VMCreateStatusResult{}, nil
|
||||
}
|
||||
vmCreateCancelFunc = func(context.Context, string, string) error {
|
||||
t.Fatal("vmCreateCancelFunc should not be called")
|
||||
return nil
|
||||
}
|
||||
guestWaitForSSHFunc = func(ctx context.Context, address, privateKeyPath string, interval time.Duration) error {
|
||||
return nil
|
||||
}
|
||||
guestDialFunc = func(ctx context.Context, address, privateKeyPath string) (vmRunGuestClient, error) {
|
||||
return &testVMRunGuestClient{}, nil
|
||||
}
|
||||
prepareVMRunRepoCopyFunc = func(ctx context.Context, spec vmRunRepoSpec) (string, func(), error) {
|
||||
return t.TempDir(), func() {}, nil
|
||||
}
|
||||
hostOpencodeAttachSupportedFunc = func(context.Context) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
opencodeExecFunc = func(ctx context.Context, stdin io.Reader, stdout, stderr io.Writer, args []string) error {
|
||||
t.Fatalf("opencodeExecFunc should not be called when host attach is unsupported: %v", args)
|
||||
return nil
|
||||
}
|
||||
var sshArgs []string
|
||||
sshExecFunc = func(ctx context.Context, stdin io.Reader, stdout, stderr io.Writer, args []string) error {
|
||||
sshArgs = append([]string(nil), args...)
|
||||
return nil
|
||||
}
|
||||
|
||||
var stderr bytes.Buffer
|
||||
err := runVMRun(
|
||||
context.Background(),
|
||||
"/tmp/bangerd.sock",
|
||||
model.DaemonConfig{SSHKeyPath: "/tmp/id_ed25519"},
|
||||
strings.NewReader(""),
|
||||
&bytes.Buffer{},
|
||||
&stderr,
|
||||
api.VMCreateParams{Name: "devbox"},
|
||||
vmRunRepoSpec{RepoRoot: repoRoot, RepoName: "repo", HeadCommit: "deadbeef"},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("runVMRun: %v", err)
|
||||
}
|
||||
if len(sshArgs) < 3 {
|
||||
t.Fatalf("sshArgs = %v, want fallback SSH invocation", sshArgs)
|
||||
}
|
||||
if sshArgs[len(sshArgs)-3] != "bash" || sshArgs[len(sshArgs)-2] != "-lc" {
|
||||
t.Fatalf("sshArgs = %v, want bash -lc fallback command", sshArgs)
|
||||
}
|
||||
if sshArgs[len(sshArgs)-1] != "cd '/root/repo' && exec opencode ." {
|
||||
t.Fatalf("ssh fallback command = %q, want guest opencode launch", sshArgs[len(sshArgs)-1])
|
||||
}
|
||||
if !strings.Contains(stderr.String(), "[vm run] host opencode has no attach support; starting guest opencode over ssh") {
|
||||
t.Fatalf("stderr = %q, want SSH fallback progress", stderr.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestOpencodeAttachHelpOutputSupported(t *testing.T) {
|
||||
if !opencodeAttachHelpOutputSupported([]byte("opencode attach [url]\n\nAttach a terminal")) {
|
||||
t.Fatal("expected attach help output to be recognized")
|
||||
}
|
||||
if opencodeAttachHelpOutputSupported([]byte("opencode [project]\n\nCommands:\n opencode run [message..]")) {
|
||||
t.Fatal("unexpected attach support for top-level help output")
|
||||
}
|
||||
}
|
||||
|
||||
func TestVMRunToolingHarnessScriptUsesMiseOnly(t *testing.T) {
|
||||
script := vmRunToolingHarnessScript(vmRunRepoSpec{RepoName: "repo"}, toolingplan.Plan{
|
||||
Steps: []toolingplan.InstallStep{{Tool: "go", Version: "1.25.0", Source: "go.mod"}},
|
||||
Skips: []toolingplan.SkipNote{{Target: "python", Reason: "no .python-version"}},
|
||||
})
|
||||
for _, want := range []string{
|
||||
`if [ -f .mise.toml ] || [ -f .tool-versions ]; then`,
|
||||
"PROMPT_FILE=" + shellQuote(vmRunToolingHarnessPromptPath("repo")),
|
||||
fmt.Sprintf("INSTALL_TIMEOUT_SECS=%d", vmRunToolingInstallTimeoutSeconds),
|
||||
"MODEL=" + shellQuote(vmRunToolingHarnessModel),
|
||||
fmt.Sprintf("TIMEOUT_SECS=%d", vmRunToolingHarnessTimeoutSeconds),
|
||||
`run_best_effort "$MISE_BIN" install`,
|
||||
`deterministic install: go@1.25.0 (go.mod)`,
|
||||
`run_bounded_best_effort "$INSTALL_TIMEOUT_SECS" "$MISE_BIN" use -g --pin 'go@1.25.0'`,
|
||||
`deterministic skip: python (no .python-version)`,
|
||||
`run_best_effort "$MISE_BIN" reshim`,
|
||||
`run_bounded_best_effort "$TIMEOUT_SECS" bash -lc 'exec "$1" run --format json -m "$2" "$(cat "$3")"' _ "$OPENCODE_BIN" "$MODEL" "$PROMPT_FILE"`,
|
||||
`command timed out after ${timeout_secs}s: $*`,
|
||||
`tooling prompt file missing: $PROMPT_FILE`,
|
||||
} {
|
||||
if !strings.Contains(script, want) {
|
||||
t.Fatalf("script = %q, want %q", script, want)
|
||||
}
|
||||
}
|
||||
for _, unwanted := range []string{"git add", "cat > .mise.toml", "cat > .tool-versions"} {
|
||||
if strings.Contains(script, unwanted) {
|
||||
t.Fatalf("script = %q, want no %q", script, unwanted)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrepareVMRunRepoCopyCreatesShallowMetadataCopy(t *testing.T) {
|
||||
if _, err := exec.LookPath("git"); err != nil {
|
||||
t.Skip("git not installed")
|
||||
|
|
@ -1538,9 +1820,24 @@ func testRunGit(t *testing.T, dir string, args ...string) string {
|
|||
return string(output)
|
||||
}
|
||||
|
||||
type testVMRunUpload struct {
|
||||
path string
|
||||
mode os.FileMode
|
||||
data []byte
|
||||
}
|
||||
|
||||
type testVMRunGuestClient struct {
|
||||
closed bool
|
||||
uploads []testVMRunUpload
|
||||
uploadPath string
|
||||
uploadMode os.FileMode
|
||||
uploadData []byte
|
||||
uploadErr error
|
||||
checkoutErr error
|
||||
launchErr error
|
||||
script string
|
||||
launchScript string
|
||||
runScriptCalls int
|
||||
tarSourceDir string
|
||||
tarCommand string
|
||||
streamSourceDir string
|
||||
|
|
@ -1553,6 +1850,15 @@ func (c *testVMRunGuestClient) Close() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (c *testVMRunGuestClient) UploadFile(ctx context.Context, remotePath string, mode os.FileMode, data []byte, logWriter io.Writer) error {
|
||||
copyData := append([]byte(nil), data...)
|
||||
c.uploads = append(c.uploads, testVMRunUpload{path: remotePath, mode: mode, data: copyData})
|
||||
c.uploadPath = remotePath
|
||||
c.uploadMode = mode
|
||||
c.uploadData = copyData
|
||||
return c.uploadErr
|
||||
}
|
||||
|
||||
func (c *testVMRunGuestClient) StreamTar(ctx context.Context, sourceDir, remoteCommand string, logWriter io.Writer) error {
|
||||
c.tarSourceDir = sourceDir
|
||||
c.tarCommand = remoteCommand
|
||||
|
|
@ -1560,8 +1866,13 @@ func (c *testVMRunGuestClient) StreamTar(ctx context.Context, sourceDir, remoteC
|
|||
}
|
||||
|
||||
func (c *testVMRunGuestClient) RunScript(ctx context.Context, script string, logWriter io.Writer) error {
|
||||
c.script = script
|
||||
return nil
|
||||
c.runScriptCalls++
|
||||
if c.runScriptCalls == 1 {
|
||||
c.script = script
|
||||
return c.checkoutErr
|
||||
}
|
||||
c.launchScript = script
|
||||
return c.launchErr
|
||||
}
|
||||
|
||||
func (c *testVMRunGuestClient) StreamTarEntries(ctx context.Context, sourceDir string, entries []string, remoteCommand string, logWriter io.Writer) error {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue