Refuse a different handle when an Artifact Registry is already activated
What does this MR do and why?
An organization that already holds an Artifact Registry could call artifactRegistryActivate again with any handle and get errors: [] plus the handle it really holds. The same request racing the original claim gets Artifact Registry's 409. monolith/S10 criterion 3 was amended in gitlab-org/ops/artifact-registry!2946 (merged) so the sequential case answers the same way. This MR implements that in the monolith.
With a mapping row present, ArtifactRegistry::ProvisionNamespaceService now reads the namespace live (GET /api/gitlab/v1/namespaces/:uuid, never the render-path cache) and:
| Live read says | Result | Provision call |
|---|---|---|
| same handle | success, existing mapping + the live namespace on the payload | no |
| different handle (even one that also breaks a syntactic rule) | error, reason: :already_activated, message names only the claimed handle, payload carries mapping + namespace |
no |
status purged |
error, reason: :reonboarding_unavailable |
no |
AR 404 for the UUID |
error, reason: :service_unavailable (the mutation renders its own service-unavailable message) |
no |
Client::UnavailableError |
error, reason: :service_unavailable |
no |
Client::AuthorizationError |
propagates; the mutation's shared RendersErrors maps it to not-available, as on the provision path |
no |
Mutations::ArtifactRegistry::Activate returns the claimed registry alongside the payload errors for :already_activated (so the setup page can send the owner there); every other refusal keeps registry: null. The submitted handle is never persisted, logged, forwarded, or echoed. After a successful live read the service expires the render-path cache, as the condition mutations do, so the settings page does not show a stale status for up to five minutes.
Because every success payload now carries the live namespace, the mutation's cache-reading fallback (resolved_registry) became unreachable and its comment false, so it is removed.
Changes
ee/app/services/artifact_registry/provision_namespace_service.rb—resolve_existingreplaces the blanket early return; two new refusals; class doc reworded.ee/app/graphql/mutations/artifact_registry/activate.rb—:already_activatedbranch returns the registry; dead cache fallback removed;registryfield description updated (docs + introspection regenerated).ee/lib/artifact_registry/namespace.rb—PURGED_STATUS, on the value object that ownsstatus.locale/gitlab.pot— two newArtifactRegistry|strings.- Specs: one case per arm at the service and request layer, each asserting no provisioning call; a cached-entry-disagrees-with-live case; a logger spy proving no log line carries the submitted handle; response-body checks proving the submitted handle is never echoed.
No changelog: artifact_registry_ui is default_enabled: false. No frontend change here; the setup-page state the spec adds follows in a stacked MR.
Known carry-over, not changed here: the :service_unavailable ServiceResponse drops the AR request_id on both the provision and the repeat path; the mutation re-raises with the message only.
Changes since the first review (4ead4adb)
registry_forcoerces a missing livestatustounknown(thenull: falsefield), asConditionMutationdoes.- The render-path cache is expired after every successful live read, wrapped so a cache failure cannot fail the claim.
PURGED_STATUSmoved toArtifactRegistry::Namespace(ee/lib/artifact_registry/namespace.rb); the dead 404-arms_()string is gone; theregistryfield description no longer says "provisioned" for the repeat arms.- Specs: negatively-cached-entry arm, stale-cache examples assert the refresh, row asserted by attributes, request-spec fixture status is
suspended, stubs constrained by argument, request-layer timeout duplicate removed.
Changes since the second review (c3755ee4)
- The render-path cache is expired after every live read that answered, including an AR
404, so the settings page stops serving a vanished registry asactive. Only the raised-exception arms leave it alone. registry_forreturns a null registry for a payload without a namespace instead of raising (unreachable today; matches the field description).- The doubled render-path client is released after each stale-cache example.
- Externalizing the pre-existing sibling refusal messages is tracked in #631281.
References
- Work item: #619522 (closed)
- Spec amendment (merged): gitlab-org/ops/artifact-registry!2946 (merged)
- Spec: https://gitlab.com/gitlab-org/ops/artifact-registry/-/blob/main/docs/specs/monolith/S10-activation-and-deactivation.md (The mapping row, criterion 3, Error cases)
- Review thread that raised it: !250653 (comment 3717332081)
Screenshots or screen recordings
Backend only. Verified live against a GDK organization activated as gdk with a local Artifact Registry:
| Call | Before | After |
|---|---|---|
artifactRegistryActivate(slug: "totally-different-slug") |
errors: [], registry.slug: "gdk" |
errors: ["Artifact Registry is already activated for this organization with the handle 'gdk'."], registry.slug: "gdk"; submitted slug absent from the response |
artifactRegistryActivate(slug: "gdk") |
errors: [], registry.slug: "gdk" |
unchanged |
| AR namespace row + mapping row | unchanged | unchanged |
How to set up and validate locally
Needs an organization with exactly one top-level group that is already activated, and a reachable Artifact Registry.
-
Run the specs:
VITE_ENABLED=true bin/rspec ee/spec/services/artifact_registry/provision_namespace_service_spec.rb \ ee/spec/requests/api/graphql/mutations/artifact_registry/activate_spec.rb -
In
rails console, withorgan activated organization androotan owner:q = 'mutation($slug: String!) { artifactRegistryActivate(input: { slug: $slug }) { errors registry { slug status } } }' GitlabSchema.execute(q, variables: { slug: 'some-other-handle' }, context: { current_user: root, current_organization: org }).to_h # => errors names the claimed handle, registry carries it, 'some-other-handle' appears nowhere GitlabSchema.execute(q, variables: { slug: org.artifact_registry_namespace_mapping.registry.slug }, context: { current_user: root, current_organization: org }).to_h # => errors: [], registry unchanged -
Confirm
org.artifact_registry_namespace_mappingis the same row and the AR namespace is unchanged.
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.