Loading
feat(datastore): migration runner app.Component (S04 Step 2)
Summary
Step 2 of the database composition-root plan: ships the migration runner
as an app.Component so the composition root can register it alongside
the rest of LabKit's lifecycle. The runner opens the LabKit migration
client, runs migrations.Up through client.DB() under a 5-minute
timeout that caps the goose advisory-lock retry budget, then closes the
pool before returning.
Sibling-parallel with merged Step 1 (!310 (merged)); Step 3 will wire both into
cmd/artifact-registry/main.go.
References
- Plan:
docs/plans/2026-05-26-database-composition-root.md, Step 2 - Spec:
docs/specs/S04-database.md(Migrations, Connectivity, Advisory locking)
What ships
internal/datastore/migrations/runner.go:Runner(Start,Shutdown,Check,Name),NewRunner, themigrationClienttest seam, and theupFuncinjection seam.Startis strictly exactly-once per instance;Shutdownis a no-op becauseStartalready released the pool.internal/datastore/migrations/runner_test.go: 17 unit tests covering every plan acceptance criterion plus defensive hardening (re-entry guard, bounded Shutdown deadline, nil-arg panics, ctx propagation, structured log emission on Shutdown error and*goose.PartialError).internal/datastore/migrations/runner_integration_test.go: 3 tests under//go:build integrationcovering migration application, idempotency on second Start, and OTel tracing throughclient.DB().
MR-review improvements
Additive hardenings from the local PR-review pass; none remove planned behavior. The plan's Step 2 entry records these (commit `9d91f9f0`):
- Deferred `client.Shutdown` runs under `context.WithTimeout(context.WithoutCancel(ctx), 30s)` instead of `context.Background()` so a stuck teardown cannot wedge boot.
- `Start` carries a `started atomic.Bool` re-entry guard.
- `NewRunner` panics on nil `client` or `logger` (mirrors `server.NewShutdownDelay`).
- Shutdown errors and `*goose.PartialError` failures emit structured slog records under `fields.ErrorMessage`.
Test plan
- `go test -race ./internal/datastore/migrations/` (17 unit tests pass under -race)
- `go test -tags=integration -race ./internal/datastore/migrations/` (3 integration tests pass against testcontainers Postgres)
- `go vet ./...` clean
- `golangci-lint run ./internal/datastore/migrations/...` 0 issues
- CI pipeline green
Follow-ups
- #104 Manual validation: migration runner in Kubernetes cluster: k8s scenarios testcontainers does not exercise (probe interactions, rolling-deploy lock contention, SIGTERM, time-budget edge).
Edited by João Pereira