daemon split (3/5): extract *WorkspaceService service
Third phase of splitting the daemon god-struct. WorkspaceService now
owns workspace.prepare / workspace.export plus the ssh-key +
git-identity + arbitrary-file sync that runs as part of VM start's
prepare_work_disk capability hook. workspaceLocks (the per-VM tar
serialisation set) lives on the service.
workspace.go and vm_authsync.go flipped receivers from *Daemon to
*WorkspaceService. The workspaceInspectRepo / workspaceImport test
seams moved onto the service as fields.
Peer-service dependencies go through narrow function-typed fields:
vmResolver, aliveChecker, waitGuestSSH, dialGuest, imageResolver,
imageWorkSeed, withVMLockByRef, beginOperation. WorkspaceService
never touches VMService / HostNetwork / ImageService directly —
only the exact operations the Daemon hands it at construction.
Daemon lazy-init helper workspaceSvc() mirrors the Phase 1/2
pattern. Test literals still write `&Daemon{store: db, runner: r}`
and get a wired workspace service for free. Tests that override the
inspect/import seams (workspace_test.go, ~4 sites) assign them on
d.workspaceSvc() instead of on the daemon literal.
Dispatch in daemon.go: vm.workspace.prepare and vm.workspace.export
now forward one-liners to d.workspaceSvc().
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
d7614a3b2b
commit
c0d456e734
8 changed files with 202 additions and 94 deletions
110
internal/daemon/workspace_service.go
Normal file
110
internal/daemon/workspace_service.go
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
package daemon
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
"time"
|
||||
|
||||
ws "banger/internal/daemon/workspace"
|
||||
"banger/internal/model"
|
||||
"banger/internal/paths"
|
||||
"banger/internal/store"
|
||||
"banger/internal/system"
|
||||
)
|
||||
|
||||
// WorkspaceService owns workspace.prepare / workspace.export plus the
|
||||
// ssh-key + git-identity sync that runs as part of VM start's
|
||||
// prepare_work_disk capability hook. The workspaceLocks set lives here
|
||||
// so its scope (serialise concurrent tar imports on the same VM) is
|
||||
// obvious at the field definition.
|
||||
//
|
||||
// The inspect/import test seams are per-service fields so tests inject
|
||||
// fakes without mutating package-level state.
|
||||
type WorkspaceService struct {
|
||||
runner system.CommandRunner
|
||||
logger *slog.Logger
|
||||
config model.DaemonConfig
|
||||
layout paths.Layout
|
||||
store *store.Store
|
||||
|
||||
// workspaceLocks serialises concurrent workspace.prepare /
|
||||
// workspace.export on the same VM. Separate from vmLocks so slow
|
||||
// guest I/O doesn't block lifecycle ops.
|
||||
workspaceLocks vmLockSet
|
||||
|
||||
// Peer-service access via narrow function-typed dependencies.
|
||||
// WorkspaceService doesn't hold pointers to the full VMService or
|
||||
// HostNetwork; it only sees the exact operations it needs.
|
||||
vmResolver func(ctx context.Context, idOrName string) (model.VMRecord, error)
|
||||
aliveChecker func(vm model.VMRecord) bool
|
||||
waitGuestSSH func(ctx context.Context, address string, interval time.Duration) error
|
||||
dialGuest func(ctx context.Context, address string) (guestSSHClient, error)
|
||||
imageResolver func(ctx context.Context, idOrName string) (model.Image, error)
|
||||
imageWorkSeed func(ctx context.Context, image model.Image, fingerprint string) error
|
||||
withVMLockByRef func(ctx context.Context, idOrName string, fn func(model.VMRecord) (model.VMRecord, error)) (model.VMRecord, error)
|
||||
|
||||
beginOperation func(name string, attrs ...any) *operationLog
|
||||
|
||||
// Test seams.
|
||||
workspaceInspectRepo func(ctx context.Context, sourcePath, branchName, fromRef string) (ws.RepoSpec, error)
|
||||
workspaceImport func(ctx context.Context, client ws.GuestClient, spec ws.RepoSpec, guestPath string, mode model.WorkspacePrepareMode) error
|
||||
}
|
||||
|
||||
type workspaceServiceDeps struct {
|
||||
runner system.CommandRunner
|
||||
logger *slog.Logger
|
||||
config model.DaemonConfig
|
||||
layout paths.Layout
|
||||
store *store.Store
|
||||
vmResolver func(ctx context.Context, idOrName string) (model.VMRecord, error)
|
||||
aliveChecker func(vm model.VMRecord) bool
|
||||
waitGuestSSH func(ctx context.Context, address string, interval time.Duration) error
|
||||
dialGuest func(ctx context.Context, address string) (guestSSHClient, error)
|
||||
imageResolver func(ctx context.Context, idOrName string) (model.Image, error)
|
||||
imageWorkSeed func(ctx context.Context, image model.Image, fingerprint string) error
|
||||
withVMLockByRef func(ctx context.Context, idOrName string, fn func(model.VMRecord) (model.VMRecord, error)) (model.VMRecord, error)
|
||||
beginOperation func(name string, attrs ...any) *operationLog
|
||||
}
|
||||
|
||||
func newWorkspaceService(deps workspaceServiceDeps) *WorkspaceService {
|
||||
return &WorkspaceService{
|
||||
runner: deps.runner,
|
||||
logger: deps.logger,
|
||||
config: deps.config,
|
||||
layout: deps.layout,
|
||||
store: deps.store,
|
||||
vmResolver: deps.vmResolver,
|
||||
aliveChecker: deps.aliveChecker,
|
||||
waitGuestSSH: deps.waitGuestSSH,
|
||||
dialGuest: deps.dialGuest,
|
||||
imageResolver: deps.imageResolver,
|
||||
imageWorkSeed: deps.imageWorkSeed,
|
||||
withVMLockByRef: deps.withVMLockByRef,
|
||||
beginOperation: deps.beginOperation,
|
||||
}
|
||||
}
|
||||
|
||||
// workspaceSvc is Daemon's lazy-init getter. Mirrors hostNet() /
|
||||
// imageSvc() so test literals like &Daemon{store: db, runner: r, ...}
|
||||
// still get a functional WorkspaceService without spelling one out.
|
||||
func (d *Daemon) workspaceSvc() *WorkspaceService {
|
||||
if d.ws != nil {
|
||||
return d.ws
|
||||
}
|
||||
d.ws = newWorkspaceService(workspaceServiceDeps{
|
||||
runner: d.runner,
|
||||
logger: d.logger,
|
||||
config: d.config,
|
||||
layout: d.layout,
|
||||
store: d.store,
|
||||
vmResolver: d.FindVM,
|
||||
aliveChecker: d.vmAlive,
|
||||
waitGuestSSH: d.waitForGuestSSH,
|
||||
dialGuest: d.dialGuest,
|
||||
imageResolver: d.FindImage,
|
||||
imageWorkSeed: d.imageSvc().refreshManagedWorkSeedFingerprint,
|
||||
withVMLockByRef: d.withVMLockByRef,
|
||||
beginOperation: d.beginOperation,
|
||||
})
|
||||
return d.ws
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue