daemon: agent-driven workspace images — repo→image registry (GET/PUT/DELETE /api/repos), spawn resolves custom image, self-healing lvmh-ops control container with docker.sock; 132 tests, e2e 87/87

This commit is contained in:
Raphael Westphal
2026-08-18 23:54:09 +02:00
parent 8fcaeb7c03
commit 7287b831e4
15 changed files with 1216 additions and 38 deletions
+96
View File
@@ -1187,3 +1187,99 @@ func TestSpawnerStartDedupesImageList(t *testing.T) {
}
f.assertNoUnknown(t)
}
// TestSpawnerCustomImageUsed: a repo-registered image replaces the default
// worker image in the created container.
func TestSpawnerCustomImageUsed(t *testing.T) {
useFakeGit(t, fakeGitModeOK)
f := newFakeDocker()
custom := "lvmh-worker-group--project-ab12cd"
f.imageTags = map[string]bool{imageRefWorker: true, custom: true}
sp, store := newTestSpawner(t, f)
if err := store.SetRepoImage("group/project", custom); err != nil {
t.Fatalf("set repo image: %v", err)
}
res, err := sp.Start(context.Background(), "group/project", "")
if err != nil {
t.Fatalf("Start: %v", err)
}
waitJobState(t, sp, res.SessionID, stateRunning)
creates := f.createsByName("lvmh-agent-")
if len(creates) != 1 || creates[0].Image != custom {
t.Fatalf("agent create image = %+v, want %q", creates[0], custom)
}
// seed container still uses the default worker image
seed := f.createsByName("lvmh-seed-")
if len(seed) != 1 || seed[0].Image != imageRefWorker {
t.Fatalf("seed create image = %+v, want default %q", seed[0], imageRefWorker)
}
f.assertNoUnknown(t)
}
// TestSpawnerCustomImageMissing: registering an image that was never built
// fails the job with the ask-ops message instead of a raw docker error.
func TestSpawnerCustomImageMissing(t *testing.T) {
useFakeGit(t, fakeGitModeOK)
f := newFakeDocker()
f.imageTags = map[string]bool{imageRefWorker: true} // custom absent
sp, store := newTestSpawner(t, f)
custom := "lvmh-worker-group--project-ab12cd"
if err := store.SetRepoImage("group/project", custom); err != nil {
t.Fatalf("set repo image: %v", err)
}
res, err := sp.Start(context.Background(), "group/project", "")
if err != nil {
t.Fatalf("Start: %v", err)
}
job := waitJobState(t, sp, res.SessionID, stateError)
want := "custom image " + custom + " not built (ask the ops agent to build it)"
if job.Message != want {
t.Fatalf("job message = %q, want %q", job.Message, want)
}
if len(f.createsByName("lvmh-agent-")) != 0 {
t.Fatal("no agent container may be created for a missing image")
}
f.assertNoUnknown(t)
}
// TestSpawnerCustomImageDeleteFallsBack: deleting the registration returns
// the repo to the default worker image.
func TestSpawnerCustomImageDeleteFallsBack(t *testing.T) {
useFakeGit(t, fakeGitModeOK)
f := newFakeDocker()
sp, store := newTestSpawner(t, f)
if err := store.SetRepoImage("group/project", "lvmh-worker-x"); err != nil {
t.Fatalf("set repo image: %v", err)
}
if err := store.DeleteRepoImage("group/project"); err != nil {
t.Fatalf("delete repo image: %v", err)
}
res, err := sp.Start(context.Background(), "group/project", "")
if err != nil {
t.Fatalf("Start: %v", err)
}
waitJobState(t, sp, res.SessionID, stateRunning)
creates := f.createsByName("lvmh-agent-")
if len(creates) != 1 || creates[0].Image != imageRefWorker {
t.Fatalf("create after deregister = %+v, want default %q", creates[0], imageRefWorker)
}
f.assertNoUnknown(t)
}
// TestSpawnerCustomImageCheckDockerDead: the custom-image presence check
// wraps docker unavailability like Start does.
func TestSpawnerCustomImageCheckDockerDead(t *testing.T) {
f := newFakeDocker()
sp, store := newTestSpawner(t, f)
if err := store.SetRepoImage("group/project", "lvmh-worker-x"); err != nil {
t.Fatalf("set repo image: %v", err)
}
sp.images0AndDead(t)
_, err := sp.createAndStart(context.Background(), "group/project", repoSlug("group/project"), "s1")
if err == nil || !strings.Contains(err.Error(), "docker unavailable") {
t.Fatalf("createAndStart with dead docker = %v, want docker unavailable", err)
}
}