daemon: integrate QoL slices 1-5 (model/rename/catalog, history deletes, stats, repo prepare+imageUsed+built) — 95.3% cover green
This commit is contained in:
+30
-20
@@ -118,10 +118,12 @@ type SpawnJob struct {
|
||||
UpdatedAt int64 `json:"-"`
|
||||
}
|
||||
|
||||
// SpawnResult is the immediate POST /api/spawn response.
|
||||
// SpawnResult is the immediate POST /api/spawn response. ImageUsed is the
|
||||
// image the job will create the container from (registry lookup; the build
|
||||
// itself may still fail on a missing custom image).
|
||||
type SpawnResult struct {
|
||||
SessionID string `json:"sessionId"`
|
||||
ContainerID string `json:"containerId"`
|
||||
SessionID string `json:"sessionId"`
|
||||
ImageUsed string `json:"imageUsed"`
|
||||
}
|
||||
|
||||
// Spawner owns the docker client, per-repo clone serialization and job state.
|
||||
@@ -242,7 +244,16 @@ func (s *Spawner) Start(ctx context.Context, repo, branch string) (SpawnResult,
|
||||
sessionID := newUUID()
|
||||
s.setJob(sessionID, repo, stateCloning, "", "")
|
||||
go s.runJob(repo, branch, sessionID)
|
||||
return SpawnResult{SessionID: sessionID}, nil
|
||||
return SpawnResult{SessionID: sessionID, ImageUsed: s.resolveImage(repo)}, nil
|
||||
}
|
||||
|
||||
// resolveImage returns the image a repo's spawns use: the registered custom
|
||||
// image when present, else the default worker image.
|
||||
func (s *Spawner) resolveImage(repo string) string {
|
||||
if custom, ok, _ := s.store.GetRepoImage(repo); ok && custom != "" {
|
||||
return custom
|
||||
}
|
||||
return imageRefWorker
|
||||
}
|
||||
|
||||
func (s *Spawner) imageExists(ctx context.Context) (bool, error) {
|
||||
@@ -287,7 +298,7 @@ func (s *Spawner) runJob(repo, branch, sessionID string) {
|
||||
return
|
||||
}
|
||||
s.setJob(sessionID, repo, stateCreating, "", "")
|
||||
containerID, err := s.createAndStart(s.ctx, repo, slug, sessionID)
|
||||
containerID, image, err := s.createAndStart(s.ctx, repo, slug, sessionID)
|
||||
if err != nil {
|
||||
s.setJob(sessionID, repo, stateError, "", err.Error())
|
||||
return
|
||||
@@ -296,7 +307,7 @@ func (s *Spawner) runJob(repo, branch, sessionID string) {
|
||||
s.setJob(sessionID, repo, stateError, containerID, "container started but not persisted: "+err.Error())
|
||||
return
|
||||
}
|
||||
s.setJob(sessionID, repo, stateRunning, containerID, "")
|
||||
s.setJob(sessionID, repo, stateRunning, containerID, "image "+image)
|
||||
}
|
||||
|
||||
// cloneOrUpdate clones the repo into reposDir/<slug> or fast-forwards it.
|
||||
@@ -466,22 +477,21 @@ func extractBuildError(body []byte) string {
|
||||
// createAndStart provisions volumes, creates and starts the worker container.
|
||||
// A repo-registered custom image (see /api/repos) overrides the default
|
||||
// worker image; the ops agent builds and registers those.
|
||||
func (s *Spawner) createAndStart(ctx context.Context, repo, slug, sessionID string) (string, error) {
|
||||
image := imageRefWorker
|
||||
if custom, ok, _ := s.store.GetRepoImage(repo); ok && custom != "" {
|
||||
exists, err := s.imageRefExists(ctx, custom)
|
||||
func (s *Spawner) createAndStart(ctx context.Context, repo, slug, sessionID string) (string, string, error) {
|
||||
image := s.resolveImage(repo)
|
||||
if image != imageRefWorker {
|
||||
exists, err := s.imageRefExists(ctx, image)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("docker unavailable: %w", err)
|
||||
return "", "", fmt.Errorf("docker unavailable: %w", err)
|
||||
}
|
||||
if !exists {
|
||||
return "", fmt.Errorf("custom image %s not built (ask the ops agent to build it)", custom)
|
||||
return "", "", fmt.Errorf("custom image %s not built (ask the ops agent to build it)", image)
|
||||
}
|
||||
image = custom
|
||||
}
|
||||
repoVolume := volumeRepoPrefix + slug
|
||||
fresh, err := s.ensureRepoVolume(ctx, repoVolume)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return "", "", err
|
||||
}
|
||||
if fresh {
|
||||
if err := s.seedVolume(ctx, slug, repoVolume, sessionID); err != nil {
|
||||
@@ -490,14 +500,14 @@ func (s *Spawner) createAndStart(ctx context.Context, repo, slug, sessionID stri
|
||||
if rmErr := s.cli.VolumeRemove(context.Background(), repoVolume, true); rmErr != nil {
|
||||
log.Printf("spawner: remove failed seed volume %s: %v", repoVolume, rmErr)
|
||||
}
|
||||
return "", fmt.Errorf("seed %s: %w", repoVolume, err)
|
||||
return "", "", fmt.Errorf("seed %s: %w", repoVolume, err)
|
||||
}
|
||||
}
|
||||
if _, err := s.cli.VolumeCreate(ctx, volume.CreateOptions{Name: volumeSessions}); err != nil {
|
||||
return "", fmt.Errorf("volume %s: %w", volumeSessions, err)
|
||||
return "", "", fmt.Errorf("volume %s: %w", volumeSessions, err)
|
||||
}
|
||||
if _, err := s.cli.VolumeCreate(ctx, volume.CreateOptions{Name: volumePiCache}); err != nil {
|
||||
return "", fmt.Errorf("volume %s: %w", volumePiCache, err)
|
||||
return "", "", fmt.Errorf("volume %s: %w", volumePiCache, err)
|
||||
}
|
||||
|
||||
binds := []string{
|
||||
@@ -526,13 +536,13 @@ func (s *Spawner) createAndStart(ctx context.Context, repo, slug, sessionID stri
|
||||
name := "lvmh-agent-" + strings.ReplaceAll(sessionID, "-", "")[:12]
|
||||
created, err := s.cli.ContainerCreate(ctx, cfg, hostCfg, nil, nil, name)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("docker create: %w", err)
|
||||
return "", "", fmt.Errorf("docker create: %w", err)
|
||||
}
|
||||
if err := s.cli.ContainerStart(ctx, created.ID, container.StartOptions{}); err != nil {
|
||||
s.removeContainer(context.Background(), created.ID)
|
||||
return "", fmt.Errorf("docker start: %w", err)
|
||||
return "", "", fmt.Errorf("docker start: %w", err)
|
||||
}
|
||||
return created.ID, nil
|
||||
return created.ID, image, nil
|
||||
}
|
||||
|
||||
// ensureRepoVolume creates the per-repo volume; reports whether it is fresh.
|
||||
|
||||
Reference in New Issue
Block a user