workspace.export: add base_commit to capture worker git commits
Without base_commit, export diffs against the current guest HEAD. If the worker ran git commit inside the VM, HEAD advanced and the diff came back empty — committed work was silently lost. With base_commit set to the head_commit from workspace.prepare, the diff uses that fixed point instead. After git add -A the index holds the full working state, so git diff --cached <base_commit> captures everything: committed deltas (HEAD moved past base) and any uncommitted changes on top, in one patch, applied with the same git apply flow. - WorkspaceExportParams gains base_commit - WorkspaceExportResult echoes back the ref actually used - CLI gains --base-commit flag - Tests assert scripts use the caller-supplied ref and that omitting it falls back to HEAD
This commit is contained in:
parent
94c353f317
commit
ff51b7ce21
6 changed files with 162 additions and 10 deletions
|
|
@ -17,6 +17,7 @@ import (
|
|||
// Each call to RunScriptOutput returns the next response from the queue.
|
||||
type exportGuestClient struct {
|
||||
responses []exportGuestResponse
|
||||
scripts []string
|
||||
callIndex int
|
||||
}
|
||||
|
||||
|
|
@ -31,7 +32,8 @@ func (e *exportGuestClient) RunScript(_ context.Context, _ string, _ io.Writer)
|
|||
return nil
|
||||
}
|
||||
|
||||
func (e *exportGuestClient) RunScriptOutput(_ context.Context, _ string) ([]byte, error) {
|
||||
func (e *exportGuestClient) RunScriptOutput(_ context.Context, script string) ([]byte, error) {
|
||||
e.scripts = append(e.scripts, script)
|
||||
if e.callIndex >= len(e.responses) {
|
||||
return nil, nil
|
||||
}
|
||||
|
|
@ -113,6 +115,107 @@ func TestExportVMWorkspace_HappyPath(t *testing.T) {
|
|||
if fake.callIndex != 2 {
|
||||
t.Fatalf("RunScriptOutput call count = %d, want 2", fake.callIndex)
|
||||
}
|
||||
// No base_commit provided: diff ref must be HEAD.
|
||||
for _, script := range fake.scripts {
|
||||
if !strings.Contains(script, "HEAD") {
|
||||
t.Fatalf("script missing HEAD ref: %q", script)
|
||||
}
|
||||
}
|
||||
if result.BaseCommit != "HEAD" {
|
||||
t.Fatalf("BaseCommit = %q, want HEAD", result.BaseCommit)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExportVMWorkspace_WithBaseCommit(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := context.Background()
|
||||
|
||||
apiSock := filepath.Join(t.TempDir(), "fc.sock")
|
||||
firecracker := startFakeFirecracker(t, apiSock)
|
||||
|
||||
vm := testVM("exportbox-base", "image-export", "172.16.0.105")
|
||||
vm.State = model.VMStateRunning
|
||||
vm.Runtime.State = model.VMStateRunning
|
||||
vm.Runtime.PID = firecracker.Process.Pid
|
||||
vm.Runtime.APISockPath = apiSock
|
||||
|
||||
// Simulate: worker committed inside the VM. Without base_commit the diff
|
||||
// against the new HEAD would be empty. With base_commit we capture
|
||||
// everything since the original checkout.
|
||||
patch := []byte("diff --git a/worker.go b/worker.go\nindex 0000000..abcdef 100644\n")
|
||||
names := []byte("worker.go\n")
|
||||
|
||||
fake := &exportGuestClient{
|
||||
responses: []exportGuestResponse{
|
||||
{output: patch},
|
||||
{output: names},
|
||||
},
|
||||
}
|
||||
d := newExportTestDaemonStore(t, fake)
|
||||
upsertDaemonVM(t, ctx, d.store, vm)
|
||||
|
||||
const prepareCommit = "abc1234deadbeef"
|
||||
result, err := d.ExportVMWorkspace(ctx, api.WorkspaceExportParams{
|
||||
IDOrName: vm.Name,
|
||||
BaseCommit: prepareCommit,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("ExportVMWorkspace: %v", err)
|
||||
}
|
||||
if !result.HasChanges {
|
||||
t.Fatal("HasChanges = false, want true")
|
||||
}
|
||||
if result.BaseCommit != prepareCommit {
|
||||
t.Fatalf("BaseCommit = %q, want %q", result.BaseCommit, prepareCommit)
|
||||
}
|
||||
// Both scripts must reference the caller-supplied commit, not HEAD.
|
||||
for _, script := range fake.scripts {
|
||||
if strings.Contains(script, " HEAD") {
|
||||
t.Fatalf("script used HEAD instead of base_commit: %q", script)
|
||||
}
|
||||
if !strings.Contains(script, prepareCommit) {
|
||||
t.Fatalf("script missing base_commit %q: %q", prepareCommit, script)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestExportVMWorkspace_BaseCommitFallsBackToHEAD(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := context.Background()
|
||||
|
||||
apiSock := filepath.Join(t.TempDir(), "fc.sock")
|
||||
firecracker := startFakeFirecracker(t, apiSock)
|
||||
|
||||
vm := testVM("exportbox-nobase", "image-export", "172.16.0.106")
|
||||
vm.State = model.VMStateRunning
|
||||
vm.Runtime.State = model.VMStateRunning
|
||||
vm.Runtime.PID = firecracker.Process.Pid
|
||||
vm.Runtime.APISockPath = apiSock
|
||||
|
||||
fake := &exportGuestClient{
|
||||
responses: []exportGuestResponse{
|
||||
{output: nil},
|
||||
{output: nil},
|
||||
},
|
||||
}
|
||||
d := newExportTestDaemonStore(t, fake)
|
||||
upsertDaemonVM(t, ctx, d.store, vm)
|
||||
|
||||
result, err := d.ExportVMWorkspace(ctx, api.WorkspaceExportParams{
|
||||
IDOrName: vm.Name,
|
||||
BaseCommit: "", // omitted
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("ExportVMWorkspace: %v", err)
|
||||
}
|
||||
if result.BaseCommit != "HEAD" {
|
||||
t.Fatalf("BaseCommit = %q, want HEAD when not supplied", result.BaseCommit)
|
||||
}
|
||||
for _, script := range fake.scripts {
|
||||
if !strings.Contains(script, "HEAD") {
|
||||
t.Fatalf("script missing HEAD fallback: %q", script)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestExportVMWorkspace_NoChanges(t *testing.T) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue