fix: resolve the metadata backend before the garbage-collect gate

What does this MR do?

registry garbage-collect refuses to run wherever database.enabled is prefer and the metadata has fallen back to the filesystem. Offline collection is the only reclamation available there, so those registries have none.

Database.IsEnabled() already accounts for the fallback by testing !PreferFallback, but that field is only set during server startup, by initializeMetadataDatabase, which GCCmd.RunE never calls. So prefer reads as the database being in use.

This exports the server's own resolution as handlers.ResolveMetadataBackend and calls it from both processes, so the command reads the lockfiles rather than trusting configuration alone. Each refusal now names the signal it decided on, and prefer mode reports the conflicting-lockfile state.

How each process resolves the backend

flowchart TD
    cfg["config.yml<br/>database.enabled: prefer<br/>filesystem-in-use lockfile present"]

    cfg --> s1["registry serve<br/>initializeMetadataDatabase"]
    cfg --> g1["registry garbage-collect<br/>GCCmd.RunE"]

    g1 -.->|"before this MR<br/>configured value only"| old["refuses<br/>PreferFallback still false"]

    s1 --> R
    g1 --> R

    R["handlers.ResolveMetadataBackend<br/>reads both lockfiles"]

    R --> fb["PreferFallback set<br/>IsEnabled false"]

    fb --> serves["server serves<br/>filesystem metadata"]
    fb --> collects["collection runs"]

Introduced in 7bfefd8e (feat(registry): add database prefer mode, 2025-10-31). Prefer has been the default since 0d6ff19f, so a registry can be in this state without ever having configured prefer.

Two commits, refactor then fix, left unsquashed per CONTRIBUTING's split-up-refactors rule.

Related to #2391

Reported by @niklasjanz.

Developed with AI assistance.

Verification

On a self-managed omnibus 19.4.0 instance, triggered through gitlab-ctl registry-garbage-collect against seeded storage restored identically before each arm.

database.enabled Lockfiles Binary Result
prefer filesystem-in-use v4.41.0-gitlab, and master refuses, same text
prefer filesystem-in-use master + this MR collects, driver=filesystem, 3 blobs swept
true database-in-use master + this MR refuses, quoting the configured value
prefer database-in-use master + this MR refuses, naming the absent lockfile
prefer both master + this MR ErrInvalidLockfiles
prefer both master generic message, lockfiles unmentioned
false filesystem-in-use master + this MR collects, 3 blobs swept
false filesystem-in-use master collects, the same 3 blobs

The gate is split rather than moved. An explicitly enabled database cannot resolve to anything else, so it is refused on the configured value alone, before a driver exists, because the GCS driver discovers credentials at construction and can reach the network. Only prefer mode waits on storage to decide:

database.enabled Storage configuration Binary Result
true GCS, keyfile missing master + this MR gate message, no driver error
prefer GCS, keyfile missing master + this MR driver error, no gate message
prefer GCS, keyfile missing master gate message, no driver error

So the first gate runs before any driver exists, and a prefer-mode registry with unusable storage configuration now reports that driver error rather than the refusal. Collection was already impossible in that state.

registry/root_test.go covers the command, including a case that seeds an unreferenced blob and asserts the sweep removes it, and registry/handlers/app_test.go covers the resolution directly. The five TestInitializeMetadataDatabase_* cases and TestNewApp_PreferFallback_V1RoutesReturn404 in app_integration_test.go pass unchanged on master and on this branch against PostgreSQL 17.

Author checklist

  • CODEOWNERS Review: This MR requires approval from at least one CODEOWNER per category/file.
  • Assign one of conventional-commit prefixes to the MR.
    • fix: Indicates a bug fix, triggers a patch release.
  • MR contains database changes including schema/background migrations:
  • Change contains a breaking change - apply the breaking change label.
  • Change is considered high risk - apply the label high-risk-change
  • I created or linked to an existing issue for every added or updated TODO, BUG, FIXME or OPTIMIZE prefixed comment
  • Changes cannot be rolled back

Reviewer checklist

  • Ensure the commit and MR tittle are still accurate.
  • If the change contains a breaking change, verify the breaking change label.
  • If the change is considered high risk, verify the label high-risk-change
  • Identify if the change can be rolled back safely. (note: all other reasons for not being able to rollback will be sufficiently captured by major version changes).

Merge request reports

Loading
Loading