Add artifact registry client foundation and repository fetch
What does this MR do and why?
Add artifact registry client foundation and repository fetch
This is step 1 in the plan: https://gitlab.com/gitlab-org/ops/artifact-registry/-/blob/main/docs/plans/monolith/2026-07-02-ar-ruby-client.md#step-1-client-foundation-and-repository-fetch
References
Screenshots or screen recordings
| Before | After |
|---|---|
How to set up and validate locally
You'll need to ensure you have setup the caproni Artifact Registry dev rig.
To test the AR client, if you don't have the rails monolith edit mode setup, then you can run the rails console via the gdk, the default for the base url is currently localhost:8080, so the validation steps will work.
To test via caproni, setup the monolith edit mode with ./gitlab checked out to 605077-ar-ruby-client-step-1.
Update the config/gitlab.yml file under development with the below
artifact_registry:
api_url: http://artifact-registry.artifact-registry.svc.cluster.local:8080- Bring up the caproni AR rig:
cdto theartifact-registrydir and runcaproni up. - Run
caproni updateto get the latest AR, thencaproni run(with the monolith in edit mode on this branch). - Check all pods are running:
ar_podsandgl_pods. - Seed a repository (namespace
gitlab-org, repotest) from the AR rig dir:scripts/seed-namespace.sh - Pre-flight the backend directly from your host, which isolates caproni issues from Rails:
curl -s -H "Authorization: Bearer local-dev-not-a-secret" \ http://localhost:8080/api/v1/gitlab-org/repositories/test | jq
Expected:
gitlab git:(605077-ar-ruby-client-step-1) curl -s -H "Authorization: Bearer local-dev-not-a-secret" \
http://localhost:8080/api/v1/gitlab-org/repositories/test | jq
{
"id": "f1455977-160a-4d86-93ab-5d7df2c84774",
"name": "test",
"format": "container",
"kind": "hosted",
"visibility": "public",
"description": null,
"artifacts_count": 0,
"downloads_count": 0,
"size_bytes": 0,
"created_at": "2026-07-13T04:29:13Z",
"last_updated_at": null,
"created_by": null,
"updated_by": null
}- Open a Rails console in the edit-mode monolith. From the rig's
gitlab/directory:.gitlab/caproni/exec.sh bin/rails console
OR
if running from the gdk, launch the console gdk rails console
- Call through the client. The token is injected via a stand-in
TokenExchange:token = 'local-dev-not-a-secret' token_exchange = Class.new(ArtifactRegistry::TokenExchange) do define_method(:token_for) { |_user, _slug| token } end.new client = ArtifactRegistry::Client.new(current_user: nil, token_exchange: token_exchange) repo = client.repository(slug: 'gitlab-org', name: 'test') [repo.class, repo&.name, repo&.format, repo&.kind, repo&.visibility] # => [ArtifactRegistry::Repository, "test", "container", "hosted", "public"] client.repository(slug: 'gitlab-org', name: 'nope') # => nil (404 path)
Expected:
--------------------------------------------------------------------------------
Ruby: ruby 3.3.11 (2026-03-26 revision 1f2d15125a) [arm64-darwin25]
GitLab: 19.2.0-pre (40098566a17) EE
GitLab Shell: Unknown
PostgreSQL: 17.8
--------------------------------------------------------------------------------
Loading development environment (Rails 7.2.3.1)
[1] pry(main)> token = 'local-dev-not-a-secret'
=> "local-dev-not-a-secret"
[2] pry(main)> token_exchange = Class.new(ArtifactRegistry::TokenExchange) do
[2] pry(main)> token_exchange = Class.new(ArtifactRegistry::TokenExchange) do
define_method(:token_for) { |_user, _slug| token }
=> #<#<Class:0x000000014b6cb230>:0x000000014932a2e0>
[3] pry(main)> client = ArtifactRegistry::Client.new(current_user: nil, token_exchange: token_exchange)
=> #<ArtifactRegistry::Client:0x000000014a365c90
@base_url=nil,
@current_user=nil,
@token_exchange=#<#<Class:0x000000014b6cb230>:0x000000014932a2e0>>
[4] pry(main)> repo = client.repository(slug: 'gitlab-org', name: 'test')
=> #<ArtifactRegistry::Repository:0x000000014a1e93d0
@attributes=
{"id"=>"f1455977-160a-4d86-93ab-5d7df2c84774",
"name"=>"test",
"format"=>"container",
"kind"=>"hosted",
"visibility"=>"public",
"description"=>nil,
"artifacts_count"=>0,
"downloads_count"=>0,
"size_bytes"=>0,
"created_at"=>"2026-07-13T04:29:13Z",
"last_updated_at"=>nil,
"created_by"=>nil,
"updated_by"=>nil}>
[5] pry(main)> [repo.class, repo&.name, repo&.format, repo&.kind, repo&.visibility]
=> [ArtifactRegistry::Repository, "test", "container", "hosted", "public"]
[6] pry(main)> client.repository(slug: 'gitlab-org', name: 'nope')
=> nil- Negative auth check. The default
TokenExchangereturnsnil, so a client with no injected exchange raises before any request is made:ArtifactRegistry::Client.new(current_user: nil).repository(slug: 'gitlab-org', name: 'test') # => raises ArtifactRegistry::Client::AuthorizationError
Expected:
[8] pry(main)> ArtifactRegistry::Client.new(current_user: nil).repository(slug: 'gitlab-org', name: 'test')
ArtifactRegistry::Client::AuthorizationError: No Artifact Registry credential was obtained for the current user
from /Users/fionamccawley/GitLab/caproni-demo/artifact-registry/gitlab/ee/lib/artifact_registry/client.rb:107:in `raise_missing_credential'
[9] pry(main)>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.
Related to #605077 (closed)