Add guest.session.send and vm.workspace.export RPCs
guest.session.send — write to a pipe-mode session's stdin without holding the exclusive attach. The daemon dials a fresh SSH connection, uploads the payload to a temp file, and cats it into the session's named FIFO. Linux atomicity for writes ≤ PIPE_BUF covers all pi RPC JSONL lines. Attach exclusivity is unchanged. vm.workspace.export — pull changes from guest back to host. Runs `git add -A && git diff --cached HEAD --binary` inside the guest via a new RunScriptOutput helper on guest.Client (stdout-only capture, distinct from RunScript which merges stderr). Returns a binary-safe patch and a list of changed files. CLI writes the patch to stdout for `| git apply` or to a file via --output. RunScriptOutput is implemented as a direct SSH session (same pattern as runSession) rather than going through StartCommand/StreamSession to avoid closing the underlying Client, which is required since ExportVMWorkspace calls it twice on the same connection. New files: internal/daemon/workspace_test.go
This commit is contained in:
parent
797a9de1ce
commit
94c353f317
9 changed files with 1074 additions and 1 deletions
|
|
@ -35,6 +35,56 @@ type workspaceRepoSpec struct {
|
|||
Submodules []string
|
||||
}
|
||||
|
||||
func (d *Daemon) ExportVMWorkspace(ctx context.Context, params api.WorkspaceExportParams) (api.WorkspaceExportResult, error) {
|
||||
guestPath := strings.TrimSpace(params.GuestPath)
|
||||
if guestPath == "" {
|
||||
guestPath = "/root/repo"
|
||||
}
|
||||
vm, err := d.FindVM(ctx, params.IDOrName)
|
||||
if err != nil {
|
||||
return api.WorkspaceExportResult{}, err
|
||||
}
|
||||
if vm.State != model.VMStateRunning || !system.ProcessRunning(vm.Runtime.PID, vm.Runtime.APISockPath) {
|
||||
return api.WorkspaceExportResult{}, fmt.Errorf("vm %q is not running", vm.Name)
|
||||
}
|
||||
client, err := d.dialGuest(ctx, net.JoinHostPort(vm.Runtime.GuestIP, "22"))
|
||||
if err != nil {
|
||||
return api.WorkspaceExportResult{}, fmt.Errorf("dial guest: %w", err)
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
// Stage all changes then emit a binary-safe unified diff against HEAD.
|
||||
// --binary ensures binary files are handled correctly by git apply.
|
||||
patchScript := fmt.Sprintf(
|
||||
"set -euo pipefail\ncd %s\ngit add -A\ngit diff --cached HEAD --binary\n",
|
||||
guestShellQuote(guestPath),
|
||||
)
|
||||
patch, err := client.RunScriptOutput(ctx, patchScript)
|
||||
if err != nil {
|
||||
return api.WorkspaceExportResult{}, fmt.Errorf("export workspace diff: %w", err)
|
||||
}
|
||||
|
||||
// Enumerate changed paths (index already staged; this is a cheap read).
|
||||
namesScript := fmt.Sprintf(
|
||||
"set -euo pipefail\ncd %s\ngit diff --cached HEAD --name-only\n",
|
||||
guestShellQuote(guestPath),
|
||||
)
|
||||
namesOut, _ := client.RunScriptOutput(ctx, namesScript)
|
||||
var changed []string
|
||||
for _, line := range strings.Split(strings.TrimSpace(string(namesOut)), "\n") {
|
||||
if line = strings.TrimSpace(line); line != "" {
|
||||
changed = append(changed, line)
|
||||
}
|
||||
}
|
||||
|
||||
return api.WorkspaceExportResult{
|
||||
GuestPath: guestPath,
|
||||
Patch: patch,
|
||||
ChangedFiles: changed,
|
||||
HasChanges: len(patch) > 0,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (d *Daemon) PrepareVMWorkspace(ctx context.Context, params api.VMWorkspacePrepareParams) (model.WorkspacePrepareResult, error) {
|
||||
mode, err := parseWorkspacePrepareMode(params.Mode)
|
||||
if err != nil {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue