daemon: build a work-seed during image pull, refresh doctor check

Before this change `banger image pull` (both OCI-direct and bundle
paths) shipped images with an empty WorkSeedPath — the BuildWorkSeedImage
helper existed only behind the hidden `banger internal work-seed` CLI.
Every pulled image hit ensureWorkDisk's no-seed branch, and the guest
booted with a bare /root (no .bashrc, no .profile, none of the distro
defaults).

Pull now calls BuildWorkSeedImage after the rootfs is finalised (OCI)
or fetched (bundle). The builder is behind a new `workSeedBuilder` test
seam so existing pull tests don't accidentally demand sudo mount. The
build failure is non-fatal: any error logs a warning and leaves
WorkSeedPath empty — images stay publishable even if the pulled rootfs
has no /root to extract.

Verified end-to-end by wiping the cached smoke image and re-pulling:
work-seed.ext4 lands in the artifact dir next to rootfs.ext4, and all
21 smoke scenarios pass.

Also refreshes the "feature /root work disk" fallback tooling check —
the no-seed path no longer touches mount/umount/cp after commit
0e28504, so the doctor check now only requires truncate + mkfs.ext4.
The warn copy updates from "new VM creates will be slower" to "guest
/root will be empty", which matches the actual tradeoff post-refactor.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Thales Maciel 2026-04-23 20:24:10 -03:00
parent 02773c1cf5
commit 3edd7c6de7
No known key found for this signature in database
GPG key ID: 33112E6833C34679
8 changed files with 74 additions and 19 deletions

View file

@ -45,6 +45,15 @@ func stubFinalizePulledRootfs(_ context.Context, _ string, _ imagepull.Metadata)
return nil
}
// stubWorkSeedBuilder returns an error so runBuildWorkSeed treats
// the step as non-fatal and proceeds without a work-seed. Keeps tests
// off sudo mount without asserting on WorkSeedPath.
func stubWorkSeedBuilder(_ context.Context, _ string, _ string) error {
return errWorkSeedBuilderStub
}
var errWorkSeedBuilderStub = errors.New("work-seed builder stubbed in tests")
// stubPullAndFlatten writes a fixed file tree into destDir, simulating a
// successful OCI pull without the network or tarball machinery.
func stubPullAndFlatten(_ context.Context, _ string, _ string, destDir string) (imagepull.Metadata, error) {
@ -81,6 +90,7 @@ func TestPullImageHappyPath(t *testing.T) {
runner: d.runner,
pullAndFlatten: stubPullAndFlatten,
finalizePulledRootfs: stubFinalizePulledRootfs,
workSeedBuilder: stubWorkSeedBuilder,
}
wireServices(d)
@ -132,6 +142,7 @@ func TestPullImageRejectsExistingName(t *testing.T) {
runner: d.runner,
pullAndFlatten: stubPullAndFlatten,
finalizePulledRootfs: stubFinalizePulledRootfs,
workSeedBuilder: stubWorkSeedBuilder,
}
wireServices(d)
// Seed a preexisting image with the would-be derived name.
@ -166,6 +177,7 @@ func TestPullImageRequiresKernel(t *testing.T) {
runner: d.runner,
pullAndFlatten: stubPullAndFlatten,
finalizePulledRootfs: stubFinalizePulledRootfs,
workSeedBuilder: stubWorkSeedBuilder,
}
wireServices(d)
_, err := d.img.PullImage(context.Background(), api.ImagePullParams{
@ -194,6 +206,7 @@ func TestPullImageCleansStagingOnFailure(t *testing.T) {
runner: d.runner,
pullAndFlatten: failureSeam,
finalizePulledRootfs: stubFinalizePulledRootfs,
workSeedBuilder: stubWorkSeedBuilder,
}
wireServices(d)
_, err := d.img.PullImage(context.Background(), api.ImagePullParams{