feat(store): drift state, move candidates and service merge (#10)

Adds services.awaiting_inventory, Service.Drifted/RunsRef/RunningRef, AcceptRunning, MoveCandidates, MergeSources and MergeService, and maps a job for a deleted service to ErrNotFound.
This commit is contained in:
2026-10-13 10:28:34 +02:00
parent 307e46e4c7
commit 061243f5da
11 changed files with 1036 additions and 14 deletions
+6 -2
View File
@@ -10,6 +10,7 @@ import (
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgconn"
"github.com/shcizo/package-updater/internal/imageref"
"github.com/shcizo/package-updater/internal/master/model"
"github.com/shcizo/package-updater/internal/protocol"
)
@@ -90,6 +91,9 @@ func (s *Store) CreateJob(ctx context.Context, j model.NewJob) (model.Job, error
}
return active, model.ErrJobActive
}
if errors.As(err, &pgErr) && pgErr.Code == "23503" && pgErr.ConstraintName == "update_jobs_service_id_fkey" {
return model.Job{}, model.ErrNotFound
}
if err != nil {
return model.Job{}, fmt.Errorf("create job: %w", err)
}
@@ -232,11 +236,11 @@ func (s *Store) FinishJob(ctx context.Context, agentID, jobID int64, f model.Job
// A plain tag job, whoever started it, deployed the tag at the digest
// the agent pinned; remember it. Pinned and tagless picks do not.
tagDigest := ""
if _, d, _ := strings.Cut(f.Ref, "@"); requestedDigest == "" && digestPattern.MatchString(d) {
if _, d, _ := strings.Cut(f.Ref, "@"); requestedDigest == "" && imageref.IsDigest(d) {
tagDigest = d
}
if _, err := tx.Exec(ctx, `
UPDATE services SET desired_ref = $2,
UPDATE services SET desired_ref = $2, awaiting_inventory = true,
last_tag_digest = CASE WHEN $6 <> '' THEN $6 ELSE last_tag_digest END,
available_ref = CASE WHEN available_ref = $4 AND available_at <= $3 AND ($5 = '' OR available_digest = $5) THEN '' ELSE available_ref END,
available_digest = CASE WHEN available_ref = $4 AND available_at <= $3 AND ($5 = '' OR available_digest = $5) THEN '' ELSE available_digest END,