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?, andvirtual?predicates to theArtifactRegistry::Repositoryvalue object, derived fromkind. - Gate
Resolvers::ArtifactRegistry::PackagesResolverto return nil for a virtual repository, ahead of its existing format gate and before the client is acquired. - Gate
Resolvers::ArtifactRegistry::ImagesResolverthe same way. - Name the virtual repository in the
packagesandimagesfield 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-codingkind == 'remote'. - Leave the single
packageandimagereads 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?=falseStep 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.rbOutput (✅ )
3 files inspected, no offenses detectedStep 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.rbOutput (✅ )
repository_spec.rb: 23 examples, 0 failures
packages_resolver_spec.rb + images_resolver_spec.rb: 34 examples, 0 failuresStep 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.rbOutput (✅ )
53 examples, 0 failuresThe 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)