daemon split (2/5): extract *ImageService service
Second phase of splitting the daemon god-struct. ImageService now owns
all image + kernel registry operations: register/promote/delete/pull
for images (bundle + OCI paths), the six kernel commands, and the
shared SSH-key/work-seed injection helpers. imageOpsMu (the
publication-window lock) lives on the service; so do the three OCI
pull test seams pullAndFlatten / finalizePulledRootfs / bundleFetch.
The four files images.go, images_pull.go, image_seed.go, kernels.go
flipped their receivers from *Daemon to *ImageService.
FindImage moved with the service. Daemon keeps a thin FindImage
forwarder so callers reading the dispatch code see the obvious
facade and tests that pre-date the split still compile.
flattenNestedWorkHome — called from image_seed.go, vm_authsync.go,
and vm_disk.go across future service boundaries — became a
package-level helper taking a CommandRunner explicitly. Daemon keeps
a deprecated forwarder for now; the other services will use the
package form.
Lazy-init helper imageSvc() on Daemon mirrors hostNet() from
Phase 1, so test literals like &Daemon{store: db, runner: r, ...}
that don't spell out an ImageService still get a working one.
Tests that override the image test seams (autopull_test,
concurrency_test, images_pull_test, images_pull_bundle_test) now
assign d.img = &ImageService{...seams...}; the two-statement pattern
matches what Phase 1 established for HostNetwork.
Dispatch in daemon.go is cleaner now: every image/kernel RPC handler
is a single-liner forwarding to d.imageSvc().*. Phase 5 will do the
same for VM lifecycle.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
362009d747
commit
d7614a3b2b
15 changed files with 389 additions and 209 deletions
|
|
@ -44,7 +44,7 @@ const minPullExt4Size int64 = 1 << 30 // 1 GiB
|
|||
// staging dir to the final artifact dir, insert the store row. If two
|
||||
// pulls race to the same name, the loser fails fast at the recheck
|
||||
// and its staging dir is cleaned up via defer.
|
||||
func (d *Daemon) PullImage(ctx context.Context, params api.ImagePullParams) (model.Image, error) {
|
||||
func (s *ImageService) PullImage(ctx context.Context, params api.ImagePullParams) (model.Image, error) {
|
||||
ref := strings.TrimSpace(params.Ref)
|
||||
if ref == "" {
|
||||
return model.Image{}, errors.New("reference is required")
|
||||
|
|
@ -55,9 +55,9 @@ func (d *Daemon) PullImage(ctx context.Context, params api.ImagePullParams) (mod
|
|||
return model.Image{}, fmt.Errorf("load image catalog: %w", err)
|
||||
}
|
||||
if entry, lookupErr := catalog.Lookup(ref); lookupErr == nil {
|
||||
return d.pullFromBundle(ctx, params, entry)
|
||||
return s.pullFromBundle(ctx, params, entry)
|
||||
}
|
||||
return d.pullFromOCI(ctx, params)
|
||||
return s.pullFromOCI(ctx, params)
|
||||
}
|
||||
|
||||
// publishImage is the narrow critical section shared by every image-
|
||||
|
|
@ -71,11 +71,11 @@ func (d *Daemon) PullImage(ctx context.Context, params api.ImagePullParams) (mod
|
|||
// in place, e.g. RegisterImage which only touches the store). When
|
||||
// non-empty the rename is the publication atom: finalDir must not
|
||||
// already exist before the rename fires.
|
||||
func (d *Daemon) publishImage(ctx context.Context, image model.Image, stagingDir, finalDir string) (model.Image, error) {
|
||||
d.imageOpsMu.Lock()
|
||||
defer d.imageOpsMu.Unlock()
|
||||
func (s *ImageService) publishImage(ctx context.Context, image model.Image, stagingDir, finalDir string) (model.Image, error) {
|
||||
s.imageOpsMu.Lock()
|
||||
defer s.imageOpsMu.Unlock()
|
||||
|
||||
if existing, err := d.store.GetImageByName(ctx, image.Name); err == nil {
|
||||
if existing, err := s.store.GetImageByName(ctx, image.Name); err == nil {
|
||||
return model.Image{}, fmt.Errorf("image %q already exists (id=%s); pick a different --name or delete it first", image.Name, existing.ID)
|
||||
}
|
||||
if finalDir != "" {
|
||||
|
|
@ -83,7 +83,7 @@ func (d *Daemon) publishImage(ctx context.Context, image model.Image, stagingDir
|
|||
return model.Image{}, fmt.Errorf("publish artifact dir: %w", err)
|
||||
}
|
||||
}
|
||||
if err := d.store.UpsertImage(ctx, image); err != nil {
|
||||
if err := s.store.UpsertImage(ctx, image); err != nil {
|
||||
if finalDir != "" {
|
||||
_ = os.RemoveAll(finalDir)
|
||||
}
|
||||
|
|
@ -94,7 +94,7 @@ func (d *Daemon) publishImage(ctx context.Context, image model.Image, stagingDir
|
|||
|
||||
// pullFromOCI is the original OCI-registry-pull path. See PullImage for
|
||||
// the intent.
|
||||
func (d *Daemon) pullFromOCI(ctx context.Context, params api.ImagePullParams) (image model.Image, err error) {
|
||||
func (s *ImageService) pullFromOCI(ctx context.Context, params api.ImagePullParams) (image model.Image, err error) {
|
||||
ref := strings.TrimSpace(params.Ref)
|
||||
parsed, err := name.ParseReference(ref)
|
||||
if err != nil {
|
||||
|
|
@ -108,11 +108,11 @@ func (d *Daemon) pullFromOCI(ctx context.Context, params api.ImagePullParams) (i
|
|||
return model.Image{}, errors.New("could not derive image name from ref; pass --name")
|
||||
}
|
||||
}
|
||||
if existing, lookupErr := d.store.GetImageByName(ctx, imgName); lookupErr == nil {
|
||||
if existing, lookupErr := s.store.GetImageByName(ctx, imgName); lookupErr == nil {
|
||||
return model.Image{}, fmt.Errorf("image %q already exists (id=%s); pick a different --name or delete it first", imgName, existing.ID)
|
||||
}
|
||||
|
||||
kernelPath, initrdPath, modulesDir, err := d.resolveKernelInputs(ctx, params.KernelRef, params.KernelPath, params.InitrdPath, params.ModulesDir)
|
||||
kernelPath, initrdPath, modulesDir, err := s.resolveKernelInputs(ctx, params.KernelRef, params.KernelPath, params.InitrdPath, params.ModulesDir)
|
||||
if err != nil {
|
||||
return model.Image{}, err
|
||||
}
|
||||
|
|
@ -124,7 +124,7 @@ func (d *Daemon) pullFromOCI(ctx context.Context, params api.ImagePullParams) (i
|
|||
if err != nil {
|
||||
return model.Image{}, err
|
||||
}
|
||||
finalDir := filepath.Join(d.layout.ImagesDir, id)
|
||||
finalDir := filepath.Join(s.layout.ImagesDir, id)
|
||||
stagingDir := finalDir + ".staging"
|
||||
if err := os.MkdirAll(stagingDir, 0o755); err != nil {
|
||||
return model.Image{}, err
|
||||
|
|
@ -144,7 +144,7 @@ func (d *Daemon) pullFromOCI(ctx context.Context, params api.ImagePullParams) (i
|
|||
}
|
||||
defer os.RemoveAll(rootfsTree)
|
||||
|
||||
meta, err := d.runPullAndFlatten(ctx, ref, d.layout.OCICacheDir, rootfsTree)
|
||||
meta, err := s.runPullAndFlatten(ctx, ref, s.layout.OCICacheDir, rootfsTree)
|
||||
if err != nil {
|
||||
return model.Image{}, fmt.Errorf("pull oci image: %w", err)
|
||||
}
|
||||
|
|
@ -162,14 +162,14 @@ func (d *Daemon) pullFromOCI(ctx context.Context, params api.ImagePullParams) (i
|
|||
}
|
||||
|
||||
rootfsExt4 := filepath.Join(stagingDir, "rootfs.ext4")
|
||||
if err := imagepull.BuildExt4(ctx, d.runner, rootfsTree, rootfsExt4, sizeBytes); err != nil {
|
||||
if err := imagepull.BuildExt4(ctx, s.runner, rootfsTree, rootfsExt4, sizeBytes); err != nil {
|
||||
return model.Image{}, fmt.Errorf("build rootfs ext4: %w", err)
|
||||
}
|
||||
if err := d.runFinalizePulledRootfs(ctx, rootfsExt4, meta); err != nil {
|
||||
if err := s.runFinalizePulledRootfs(ctx, rootfsExt4, meta); err != nil {
|
||||
return model.Image{}, err
|
||||
}
|
||||
|
||||
stagedKernel, stagedInitrd, stagedModules, err := imagemgr.StageBootArtifacts(ctx, d.runner, stagingDir, kernelPath, initrdPath, modulesDir)
|
||||
stagedKernel, stagedInitrd, stagedModules, err := imagemgr.StageBootArtifacts(ctx, s.runner, stagingDir, kernelPath, initrdPath, modulesDir)
|
||||
if err != nil {
|
||||
return model.Image{}, fmt.Errorf("stage boot artifacts: %w", err)
|
||||
}
|
||||
|
|
@ -187,7 +187,7 @@ func (d *Daemon) pullFromOCI(ctx context.Context, params api.ImagePullParams) (i
|
|||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
published, err := d.publishImage(ctx, image, stagingDir, finalDir)
|
||||
published, err := s.publishImage(ctx, image, stagingDir, finalDir)
|
||||
if err != nil {
|
||||
return model.Image{}, err
|
||||
}
|
||||
|
|
@ -200,12 +200,12 @@ func (d *Daemon) pullFromOCI(ctx context.Context, params api.ImagePullParams) (i
|
|||
// injected at build time), verify its sha256, and register the result
|
||||
// as a managed image. No flatten / mkfs / debugfs work on the daemon
|
||||
// host.
|
||||
func (d *Daemon) pullFromBundle(ctx context.Context, params api.ImagePullParams, entry imagecat.CatEntry) (image model.Image, err error) {
|
||||
func (s *ImageService) pullFromBundle(ctx context.Context, params api.ImagePullParams, entry imagecat.CatEntry) (image model.Image, err error) {
|
||||
imgName := strings.TrimSpace(params.Name)
|
||||
if imgName == "" {
|
||||
imgName = entry.Name
|
||||
}
|
||||
if existing, lookupErr := d.store.GetImageByName(ctx, imgName); lookupErr == nil {
|
||||
if existing, lookupErr := s.store.GetImageByName(ctx, imgName); lookupErr == nil {
|
||||
return model.Image{}, fmt.Errorf("image %q already exists (id=%s); pick a different --name or delete it first", imgName, existing.ID)
|
||||
}
|
||||
|
||||
|
|
@ -214,7 +214,7 @@ func (d *Daemon) pullFromBundle(ctx context.Context, params api.ImagePullParams,
|
|||
if kernelRef == "" && strings.TrimSpace(params.KernelPath) == "" {
|
||||
kernelRef = strings.TrimSpace(entry.KernelRef)
|
||||
}
|
||||
kernelPath, initrdPath, modulesDir, err := d.resolveKernelInputs(ctx, kernelRef, params.KernelPath, params.InitrdPath, params.ModulesDir)
|
||||
kernelPath, initrdPath, modulesDir, err := s.resolveKernelInputs(ctx, kernelRef, params.KernelPath, params.InitrdPath, params.ModulesDir)
|
||||
if err != nil {
|
||||
return model.Image{}, err
|
||||
}
|
||||
|
|
@ -226,7 +226,7 @@ func (d *Daemon) pullFromBundle(ctx context.Context, params api.ImagePullParams,
|
|||
if err != nil {
|
||||
return model.Image{}, err
|
||||
}
|
||||
finalDir := filepath.Join(d.layout.ImagesDir, id)
|
||||
finalDir := filepath.Join(s.layout.ImagesDir, id)
|
||||
stagingDir := finalDir + ".staging"
|
||||
if err := os.MkdirAll(stagingDir, 0o755); err != nil {
|
||||
return model.Image{}, err
|
||||
|
|
@ -238,7 +238,7 @@ func (d *Daemon) pullFromBundle(ctx context.Context, params api.ImagePullParams,
|
|||
}
|
||||
}()
|
||||
|
||||
if _, err := d.runBundleFetch(ctx, stagingDir, entry); err != nil {
|
||||
if _, err := s.runBundleFetch(ctx, stagingDir, entry); err != nil {
|
||||
return model.Image{}, fmt.Errorf("fetch bundle: %w", err)
|
||||
}
|
||||
// manifest.json is metadata we only need at fetch time; strip it
|
||||
|
|
@ -246,7 +246,7 @@ func (d *Daemon) pullFromBundle(ctx context.Context, params api.ImagePullParams,
|
|||
_ = os.Remove(filepath.Join(stagingDir, imagecat.ManifestFilename))
|
||||
rootfsExt4 := filepath.Join(stagingDir, imagecat.RootfsFilename)
|
||||
|
||||
stagedKernel, stagedInitrd, stagedModules, err := imagemgr.StageBootArtifacts(ctx, d.runner, stagingDir, kernelPath, initrdPath, modulesDir)
|
||||
stagedKernel, stagedInitrd, stagedModules, err := imagemgr.StageBootArtifacts(ctx, s.runner, stagingDir, kernelPath, initrdPath, modulesDir)
|
||||
if err != nil {
|
||||
return model.Image{}, fmt.Errorf("stage boot artifacts: %w", err)
|
||||
}
|
||||
|
|
@ -264,7 +264,7 @@ func (d *Daemon) pullFromBundle(ctx context.Context, params api.ImagePullParams,
|
|||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
published, err := d.publishImage(ctx, image, stagingDir, finalDir)
|
||||
published, err := s.publishImage(ctx, image, stagingDir, finalDir)
|
||||
if err != nil {
|
||||
return model.Image{}, err
|
||||
}
|
||||
|
|
@ -273,17 +273,17 @@ func (d *Daemon) pullFromBundle(ctx context.Context, params api.ImagePullParams,
|
|||
}
|
||||
|
||||
// runBundleFetch is the seam tests substitute. nil → real implementation.
|
||||
func (d *Daemon) runBundleFetch(ctx context.Context, destDir string, entry imagecat.CatEntry) (imagecat.Manifest, error) {
|
||||
if d.bundleFetch != nil {
|
||||
return d.bundleFetch(ctx, destDir, entry)
|
||||
func (s *ImageService) runBundleFetch(ctx context.Context, destDir string, entry imagecat.CatEntry) (imagecat.Manifest, error) {
|
||||
if s.bundleFetch != nil {
|
||||
return s.bundleFetch(ctx, destDir, entry)
|
||||
}
|
||||
return imagecat.Fetch(ctx, nil, destDir, entry)
|
||||
}
|
||||
|
||||
// runPullAndFlatten is the seam tests substitute. nil → real implementation.
|
||||
func (d *Daemon) runPullAndFlatten(ctx context.Context, ref, cacheDir, destDir string) (imagepull.Metadata, error) {
|
||||
if d.pullAndFlatten != nil {
|
||||
return d.pullAndFlatten(ctx, ref, cacheDir, destDir)
|
||||
func (s *ImageService) runPullAndFlatten(ctx context.Context, ref, cacheDir, destDir string) (imagepull.Metadata, error) {
|
||||
if s.pullAndFlatten != nil {
|
||||
return s.pullAndFlatten(ctx, ref, cacheDir, destDir)
|
||||
}
|
||||
pulled, err := imagepull.Pull(ctx, ref, cacheDir)
|
||||
if err != nil {
|
||||
|
|
@ -293,21 +293,21 @@ func (d *Daemon) runPullAndFlatten(ctx context.Context, ref, cacheDir, destDir s
|
|||
}
|
||||
|
||||
// runFinalizePulledRootfs applies ownership fixup and injects banger's
|
||||
// guest agents. Tests substitute via d.finalizePulledRootfs; nil →
|
||||
// guest agents. Tests substitute via s.finalizePulledRootfs; nil →
|
||||
// real implementation using debugfs + the companion vsock-agent
|
||||
// binary resolved via paths.CompanionBinaryPath.
|
||||
func (d *Daemon) runFinalizePulledRootfs(ctx context.Context, ext4File string, meta imagepull.Metadata) error {
|
||||
if d.finalizePulledRootfs != nil {
|
||||
return d.finalizePulledRootfs(ctx, ext4File, meta)
|
||||
func (s *ImageService) runFinalizePulledRootfs(ctx context.Context, ext4File string, meta imagepull.Metadata) error {
|
||||
if s.finalizePulledRootfs != nil {
|
||||
return s.finalizePulledRootfs(ctx, ext4File, meta)
|
||||
}
|
||||
if err := imagepull.ApplyOwnership(ctx, d.runner, ext4File, meta); err != nil {
|
||||
if err := imagepull.ApplyOwnership(ctx, s.runner, ext4File, meta); err != nil {
|
||||
return fmt.Errorf("apply ownership: %w", err)
|
||||
}
|
||||
vsockBin, err := paths.CompanionBinaryPath("banger-vsock-agent")
|
||||
if err != nil {
|
||||
return fmt.Errorf("locate vsock agent binary: %w", err)
|
||||
}
|
||||
if err := imagepull.InjectGuestAgents(ctx, d.runner, ext4File, imagepull.GuestAgentAssets{
|
||||
if err := imagepull.InjectGuestAgents(ctx, s.runner, ext4File, imagepull.GuestAgentAssets{
|
||||
VsockAgentBin: vsockBin,
|
||||
}); err != nil {
|
||||
return fmt.Errorf("inject guest agents: %w", err)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue