Artifact registry: Report a service outage in the page, not as a 503

What does this MR do and why?

When the Artifact Registry service does not answer, because it is down or because its domain stops resolving, the Artifact Registry UI served the instance-wide 503 page: the one titled "503: We're sorry, GitLab is currently unavailable". One dependency being unreachable read as the whole of GitLab being down. This was reported after a few minutes of the service not responding.

Both organization entry points resolve the registry on the server before they render, and both treated any failure to reach it as a 503:

  • ee/app/controllers/organizations/artifact_registry_controller.rb, the landing route /o/:org/-/artifact_registry/
  • ee/app/controllers/organizations/artifact_registry_repositories_controller.rb, the app route /o/:org/-/artifact_registry/:slug/repositories

render_503 renders errors/service_unavailable in the errors layout, which carries no navigation, no organization context, and no mention of Artifact Registry.

The failure itself comes out of ArtifactRegistry::NamespaceMapping#registry, which reads the namespace through the client and rescues every ArtifactRegistry::Client::Error into a ResolutionFailure. All of these therefore became the full-page 503: a DNS failure, a refused connection, a timeout, a TLS failure, an AR 5xx or 429, an unreadable response body, an AR 401 or 403, and any other non-2xx. The failure is negative-cached per organization for a minute, so the 503 page also outlived the outage by up to that long.

The app already knows how to say this. repositories_list.vue renders "The Artifact Registry service is unavailable." whenever its query fails after mount, and the other views have the same state. The 503 was the only thing standing between a reader and it.

Changes

  • The app route mounts instead of answering 503. It anchors the mount on the slug from the URL and lets the app report the outage where the list would be. No frontend code changed.
  • Slug enumeration is still closed. The route normally requires the requested slug to equal the resolved one, so an unknown slug and one the viewer may not see are byte-identical 404s. Resolution cannot confirm the slug while it is failing, so only the syntax is checked, through ArtifactRegistry::SlugValidator. Every well-formed slug gets the same response during an outage, and the GraphQL queries behind it then fail the same way too, so a mount the registry did not confirm says nothing about which slugs exist. Non-members never reach it: the gating concern answers 404 before anything is resolved.
  • The landing route renders an Artifact Registry page. It has no slug to anchor on, so it cannot mount the app. New view ee/app/views/organizations/artifact_registry/unavailable.html.haml puts a Pajamas empty state inside the organization layout, so the sidebar and breadcrumb survive. It uses illustrations/status/status-fail-md.svg, the illustration the container registry and Harbor connection-error states already use.
  • A slug-less registry on the landing route now answers 404. That case is an Artifact Registry 404, meaning a permanently absent namespace rather than an outage, and it is what the app route already answered it with. After this MR, Artifact Registry never calls render_503.
  • Both responses are HTTP 200. The request succeeded and the outage is reported in the page. This follows the container registry, where Registry::ConnectionErrorsHandler re-renders :index with a flag, and the search error state. Artifact Registry client failures already reach error tracking through ArtifactRegistry::ErrorReporter, so observability does not rest on the status code.
  • Two new translatable strings, with locale/gitlab.pot regenerated.

Test coverage:

  • ee/spec/requests/organizations/artifact_registry_controller_spec.rb: the two contexts that expected a 503, an authorization failure and an unreachable service, now expect the unavailable page. A new context covers the Artifact Registry 404 answering not-found.
  • ee/spec/requests/organizations/artifact_registry_repositories_controller_spec.rb: the context that expected a 503 now expects the mount. New contexts cover the mount anchoring on the requested slug when it is not the one that would resolve, and a 404 for a slug that is not well formed.
  • 56 examples across those two files pass locally. RuboCop and haml-lint are clean. No Jest changes: the in-app alert is already covered in repositories_list_spec.js.

Screenshots or screen recordings

Screenshot When this state is reached, and why
503 page served during the outage Before. Any failure on the namespace read resolved to this: the service down, its domain not resolving, a refused connection, a timeout, an AR 5xx or 429, an unreadable body, or a 401/403. Both routes answered the instance-wide 503 page, and because the failure is negative-cached for a minute, a reader could still land here after the service had recovered.
Artifact registry is unavailable page After, landing route (/o/:org/-/artifact_registry/). Where the sidebar entry and any bookmarked or shared registry URL point. It normally resolves the slug and redirects to the repositories route, so during an outage it has no slug to redirect to and none to anchor a mount on. It reports the outage itself, inside the organization, rather than handing the reader to the instance error page.
Repositories page with the service unavailable alert After, app route (/o/:org/-/artifact_registry/:slug/repositories). The slug is in the URL here, so the app mounts on it and reports the outage where the list would be. This is already what a reader sees when the service fails while the page is open; before this MR, reloading that page or arriving fresh replaced it with the 503 page instead.

How to set up and validate locally

  1. Enable the artifact_registry_ui and org_stage_experimental flags, and have an organization with an Artifact Registry namespace mapping row.

  2. Simulate the outage by writing a failure into that mapping's registry cache entry, which is the state a real outage leaves behind. In rails console, with id being the mapping's id:

    Rails.cache.write(
      ['artifact_registry', 'namespace_mapping', 'registry', id],
      { error: { class: 'ArtifactRegistry::Client::UnavailableError', status: nil,
                 request_id: nil, code: nil, message: 'Artifact Registry request failed' } },
      expires_in: 30.minutes
    )
  3. Load /o/<org>/-/artifact_registry/. Expect the unavailable page inside the organization, not the instance-wide 503 page.

  4. Load /o/<org>/-/artifact_registry/<slug>/repositories. Expect the app to mount and show the "The Artifact Registry service is unavailable." alert under the page heading.

  5. Rails.cache.delete the same key to end the simulated outage.

Both were verified this way on the GDK; the screenshots above are from that run.

References

Reported directly rather than through an issue, so there is no tracking issue to close.

Agent Notes

Three things I left alone deliberately, each worth a second opinion:

  1. The sidebar item disappears during an outage. Sidebars::Organizations::Menus::ArtifactRegistryMenu#render? needs either a resolved slug or an organization that has not been set up, and an outage gives neither, so the Artifact Registry entry vanishes from the organization sidebar even while the reader is looking at an Artifact Registry page. This reproduces locally and is visible in the screenshots above. It is the same root treatment, a failed resolution read as "no registry", but fixing it is a separate behavior and copy decision, so it is not in this MR.
  2. The app route still offers "New repository" next to the alert. Creating one during an outage would fail. That is pre-existing behavior of the list component.
  3. The unavailable page has no retry button. The description tells the reader to refresh. Happy to add one if design wants it.

The judgment call I would most like reviewed is serving 200 rather than 503. It keeps the two Artifact Registry routes consistent and keeps the page out of reach of edge-level error handling, and error tracking already sees the failure, but it does mean the outage no longer shows up as a 5xx in request metrics.

MR acceptance checklist

Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.


This MR was authored with the assistance of an AI coding agent and reviewed by @zcuddy before submission.

🤖 Generated with Claude Code

Edited by Zack Cuddy

Merge request reports

Loading
Loading