monolith/S05 virtual Step 2: Kind gate on the artifact connections

What this does

Step 2 of the monolith/S05 virtual repository detail plan, dark behind the artifact_registry_ui feature flag. A virtual repository aggregates upstreams and holds no artifacts of its own, so the GraphQL packages and images connections on the repository detail type now resolve null for a virtual repository without issuing the gated Artifact Registry call.

ArtifactRegistry::Repository gains hosted?, remote?, and virtual? predicates (derived from kind) beside its existing format predicates, and the two connection resolvers return nil on a virtual repository before the client is acquired.

Note on the predicate set: hosted?, remote?, and virtual? are added together as one kind predicate set beside the existing format predicates. virtual? gates the two connections in this step. remote? replaces the open-coded kind == 'remote' check in the npm dist-tag delete mutation (raised in review), so it ships with one caller rather than a duplicate way to ask. hosted? has no caller yet; it is kept for symmetry with the set.

Closes #628663 (closed)

Changes

  • Add hosted?, remote?, and virtual? predicates to the ArtifactRegistry::Repository value object, derived from kind.
  • Gate Resolvers::ArtifactRegistry::PackagesResolver to return nil for a virtual repository, ahead of its existing format gate and before the client is acquired.
  • Gate Resolvers::ArtifactRegistry::ImagesResolver the same way.
  • Name the virtual repository in the packages and images field descriptions, which already enumerate every null case, and recompile the GraphQL reference and introspection schema.
  • Use ArtifactRegistry::Repository#remote? in the npm dist-tag delete mutation instead of open-coding kind == 'remote'.
  • Leave the single package and image reads unchanged: Artifact Registry answers those 404 on a virtual repository, which already resolves them null, so gating them belongs to their own plan steps (monolith/S14 and monolith/S06).
  • Specs: kind predicates on the model, one positive virtual case per format on each connection (resolver and request level), and hosted/remote regression cases.

How to set up and validate locally

Step 1 — The three kind predicates answer true for exactly their own kind

%w[hosted remote virtual unknown].each do |k|
  r = ArtifactRegistry::Repository.new("kind" => k)
  puts "kind=#{k.inspect.ljust(9)} hosted?=#{r.hosted?} remote?=#{r.remote?} virtual?=#{r.virtual?}"
end
r = ArtifactRegistry::Repository.new("kind" => nil)
puts "kind=nil       hosted?=#{r.hosted?} remote?=#{r.remote?} virtual?=#{r.virtual?}"
Output (✅)
kind="hosted"  hosted?=true remote?=false virtual?=false
kind="remote"  hosted?=false remote?=true virtual?=false
kind="virtual" hosted?=false remote?=false virtual?=true
kind="unknown" hosted?=false remote?=false virtual?=false
kind=nil       hosted?=false remote?=false virtual?=false

Step 2 — RuboCop is clean on the changed Ruby

bundle exec rubocop --no-server \
  ee/lib/artifact_registry/repository.rb \
  ee/app/graphql/resolvers/artifact_registry/packages_resolver.rb \
  ee/app/graphql/resolvers/artifact_registry/images_resolver.rb
Output (✅)
3 files inspected, no offenses detected

Step 3 — The model and resolver specs pass

bundle exec rspec \
  ee/spec/lib/artifact_registry/repository_spec.rb \
  ee/spec/graphql/resolvers/artifact_registry/packages_resolver_spec.rb \
  ee/spec/graphql/resolvers/artifact_registry/images_resolver_spec.rb
Output (✅)
repository_spec.rb: 23 examples, 0 failures
packages_resolver_spec.rb + images_resolver_spec.rb: 34 examples, 0 failures

Step 4 — The request specs pass (virtual repo of each format resolves null with no list call)

bundle exec rspec \
  ee/spec/requests/api/graphql/organizations/artifact_registry_repository_packages_spec.rb \
  ee/spec/requests/api/graphql/organizations/artifact_registry_repository_images_spec.rb
Output (✅)
53 examples, 0 failures

The virtual cases stub the repository read to return kind: virtual and assert the packages/images connection resolves null while the repository still renders, and that Artifact Registry receives no list request.

Evidence

Change type: GraphQL (two resolvers). No client-class change and no frontend change, so the console and screen-recording sections are N/A.

Rails console

N/A. This is not a change to the ArtifactRegistry::Client; the new predicates live on the Repository value object and are exercised by Step 1 above.

GraphiQL

Not recorded. The virtual path cannot be driven end to end against a live Artifact Registry yet: AR does not serve a virtual repository (virtual create is refused and the upstream-list route is contract-only, tracked in gitlab-org/ops/artifact-registry#314 (closed)), so no repository(name:) read returns kind: virtual for GraphiQL to exercise. The behavior is instead proven deterministically by the request specs in Step 4, which stub the repository read as kind: virtual and assert the connection resolves null with no outbound list call.

Screen recordings

N/A. No frontend files changed; this step is backend-only and dark behind artifact_registry_ui.

Related to #628475 (closed)

Edited by Fiona McCawley

Merge request reports

Loading
Loading