Artifact Registry ADR 009:API 設計
コンテキスト
Artifact Registry は、次の制約を持つ包括的な API 設計を必要とします。
- 3 つの API カテゴリ:レジストリの概念とやり取りする管理 API、特定のクライアントが使用する厳密な仕様に従うアーティファクト管理クライアント API、そしてプラットフォームとレジストリ間の通信のための GitLab API。
- データベースのデータ構成に従う:API エンドポイントはアーティファクトフォーマットごとに構成され、リポジトリがフォーマットファミリーごとのテーブルとフィールドを持つデータベーススキーマ に一致します(
dockerとociはcontainer_*テーブルを共有します)。このマッピングは、複雑なマルチフォーマット抽象化を回避することで実装を簡素化し、フォーマット固有の最適化を可能にします。 - 3 つのリポジトリタイプ:レジストリは 3 つのリポジトリタイプをサポートします。ホスト型(プッシュされたアーティファクトのためのプライベートストレージ)、リモート(外部レジストリのためのプロキシ/キャッシュ)、仮想(ホスト型とリモートのアップストリームを組み合わせた集約された pull エンドポイント)です。このモデルは業界の慣行に一致します。3 つのタイプはすべて独立しており、個別に管理可能なエンティティです。
Artifact Registry は Organizations(根拠は ADR-001を参照)にスコープされ、すべてのお客様向け URL に現れる、不変でお客様が選択するスラッグ(ADR-022)を使用します。すべてのクライアントおよび管理エンドポイントは、API バージョンプレフィックス(またはクライアント API の場合はプロトコルプレフィックス)の後に /:slug パスセグメントを含み、すべてのリクエストを特定のスラッグにスコープします。スラッグは、組織パスの解決に依存することなく、トポロジーサービスを介した安定した Cells ルーティングを提供します。例外は 2 つあります。OCI で必須の GET /v2/ バージョンプローブ(Containerを参照)と GitLab API エンドポイント(GitLab APIを参照)です。
Artifact Registry は、メインの GitLab アプリケーションドメインとは別の、専用ドメイン(例:artifact-registry.gitlab.com)で提供されます。これにより、メインアプリケーションの Cookie、認証情報、内部ネットワークコンテキストからレジストリを分離することで、XSS および SSRF の脆弱性に対するセキュリティ態勢が劇的に改善されます。
この ADR は API サーフェス、それらに適用されるルール、エンドポイント構造を定義します。リクエストおよびレスポンスのペイロードは、Artifact Registry コードベース内の対応する OpenAPI 仕様に存在します。
バージョニング
Artifact Registry は専用ドメインで動作するため、GitLab Rails モノリスで使用される /api/v4 プレフィックスは適用されません。モノリスは、同一ドメイン上で API ルートを Web UI ルートから分離し、その API バージョンの系譜(v3 から v4 へ)を反映するために /api/v4 を使用します。スタンドアロンサービスにはどちらの懸念も存在しません。
管理 API ルートは /api/v1 パスベースのバージョンプレフィックスを使用します。/api/ プレフィックスは、同一ドメイン上で管理ルートをプロトコル固有のクライアントルートから分離し、管理 API バージョンが将来引き上げられた場合のネームスペースの衝突を防ぎます(例:OCI は /v2/ を必須とするため、裸の /v2 管理プレフィックスは衝突します)。これは業界の慣行に一致します。パスベースのバージョニングは、ヘッダー検査を必要とせずに、URL、ログ、ルーティングルールにおいてバージョンを可視に保ちます。
クライアント API はバージョンプレフィックスを使用しません。プロトコル固有のバージョニングは、必要に応じてクライアント自身によって処理されます(例:OCI は /v2/ を必須とします)。これにより、クライアントが構成する URL を可能な限り短く保ちます。
GitLab API ルートは /api/gitlab/v1 プレフィックスを使用します。明示的な gitlab セグメントにより、サーフェスは URL、ログ、ルーティングルールで可視になり、エッジが専用のパスルールを適用できます。これは、レジストリからプラットフォームへのサーフェスで /gitlab/v1/ プレフィックスを使用する Container Registry の先例に従います。
API 分類
API の表面は、異なるルールを持つ 3 つの明確なカテゴリに分けられます。
管理 API:レジストリの概念(リポジトリ、アーティファクト、ポリシーなど)に対する CRUD 操作のための REST および GraphQL API
- 認証:GitLab の標準的な REST/GraphQL 認証
- 目的:UI、自動化スクリプト、管理ツール
- フォーマット:標準的な GitLab API パターンを持つ JSON レスポンス
- ページネーション:すべてのリストエンドポイントはページネーションされ、できればキーセットページネーション戦略を使用します
アーティファクト管理クライアント API:業界標準の仕様を実装するプロトコル固有の API
- 認証:プロトコル固有(OCI 用の Bearer トークン、Maven 用の Basic 認証など)
- 目的:ネイティブクライアントの互換性(
docker、npm、mvnコマンド) - フォーマット:プロトコル固有のレスポンス(OCI Distribution Spec、Maven Repository Layout、NPM Registry API)
GitLab API:プラットフォームとレジストリ間の通信のための REST エンドポイント
- 認証:サービス間認証情報。エンドユーザー ID は使用しない
- 目的:ネームスペースのプロビジョニング、解決、サービス条件、リソース検証
- フォーマット:JSON
- 公開範囲:信頼されたプラットフォーム呼び出し元のみ
管理 API とクライアント API は、3 つのリポジトリタイプすべて(ホスト型、仮想、リモート)を提供します。クライアント API はプロトコルごとに構成されます(プロトコルごとに 1 セットのエンドポイント。OCI Distribution Spec のエンドポイントは、docker と oci の両フォーマットのリポジトリを提供します)が、管理 API は統合されたリポジトリ CRUD を共有し、フォーマットはフォーマット固有のサブリソースにのみ現れます。
URL 構造設計
管理 API ルートは リポジトリにアンカーされます。/api/v1/:slug プレフィックスはすべてのルートで必須であり、概念上のレベルとしてはカウントされません。リポジトリエンドポイントは、一意のリポジトリ名を :repository_name パラメータとして受け取ります。
すべてのリポジトリレベルのリソース(アーティファクト、ライフサイクルポリシー、アップストリームの関連付け)はリポジトリにスコープされます。これはホスト型とリモートのリポジトリが別個のフォーマットファミリーごとのテーブルにデータを保存するためです。
:format セグメントは、すべてのフォーマット固有のサブリソースに対して リポジトリの後 に現れます:/api/v1/:slug/repositories/:repository_name/:format/...(例:イメージのリスト、ライフサイクルポリシー)。リポジトリレベルのサブリソースは特定のフォーマットに専用であり、エンドポイントの構成と返される構造のカスタマイズにおいてより高い柔軟性を可能にします。これにはアーティファクト操作とフォーマット固有の構成の両方が含まれます。ネームスペースレベルのフォーマット固有の操作は :format をプレフィックスとして使用します:/api/v1/:slug/:format/statistics。
リポジトリ CRUD 自体はフォーマットフリーです。これは repositories 親テーブルがすべてのフォーマットとタイプにわたって共有されるためです。フォーマットと種類はリポジトリリソースのプロパティであり、URL セグメントではありません。
例えば:
- すべてのリポジトリのリスト:
GET /api/v1/:slug/repositories - リポジトリの作成:
POST /api/v1/:slug/repositories - リポジトリの読み取り/更新/削除:
GET/PATCH/DELETE /api/v1/:slug/repositories/:repository_name(対象概念の識別子を使用するトップレベルルート) - コンテナファミリーのリポジトリ内のイメージのリスト:
GET /api/v1/:slug/repositories/:repository_name/:format/images(フォーマット固有のサブリソース。:formatはdockerまたはoci) - ID によるイメージの取得:
GET /api/v1/:slug/repositories/:repository_name/:format/images/:image_id(アーティファクト詳細、リポジトリにスコープ)
リポジトリ名の不変性
リポジトリ名は作成時に設定され、変更できません。リポジトリの説明は可変のままです。これは、壊れたクライアント構成、名前の再取得による認可バイパス、黙ったミスルーティングを防ぎます。また、名前ベースの URL(下記を参照)を可能にし、認可ルールを簡素化します。
これは業界の慣行に一致します。詳細は gitlab-org/gitlab#592582 を参照してください。
人間にやさしい URL
リポジトリエンドポイントは、一意の名前を識別子として受け取ります。リポジトリ名は、フォーマットやリポジトリタイプに関係なく、スラッグ内でグローバルに一意でなければなりません。このグローバルな一意性制約は業界の慣行に一致し、あいまいな名前ベースのルックアップを回避します。後でフォーマットごとの一意性へと緩和しても誰も壊しませんが、厳格化は破壊的な変更となります。名前の不変性(上記を参照)により、名前ベースの URL を安定したパスセグメントとして安全にします。スラッグは不変であるため(ADR-022)、URL パス内のすべてのセグメントは人間が読めて永続的に安定しています。
API 編成
管理用 API
管理 API は GitLab の REST API 認証 を使用します。
注: :format はリポジトリの format 値(docker、oci、maven、または npm)を表します。docker と oci はコンテナファミリーを形成します。これらはコンテナアーティファクトモデル(イメージ、タグ、マニフェスト)と container_* テーブルを共有します。:format セグメントがリポジトリのフォーマットと一致しないリクエストは、404 Not Found を返します。
ネームスペースレベル API
リポジトリ管理:
GET /api/v1/:slug/repositories- すべてのリポジトリをリスト(すべてのフォーマットにわたるホスト型、仮想、リモート)。フォーマットとリポジトリタイプによるフィルタリングをサポートPOST /api/v1/:slug/repositories- リポジトリを作成GET /api/v1/:slug/repositories/:repository_name- リポジトリの詳細を取得PATCH /api/v1/:slug/repositories/:repository_name- リポジトリを更新DELETE /api/v1/:slug/repositories/:repository_name- リポジトリを削除(破壊的操作の意図を示すパラメータが必要。リポジトリの削除を参照)
リポジトリ詳細のレスポンスはポリモーフィックであり、その形状はフォーマットと種類によって異なります。
- すべてのリポジトリは、親
repositoriesテーブルからの共通フィールドを返します:name、format、kind、visibility、description、カウンター(artifacts_count、downloads_count、size_bytes)、last_updated_at。 - フォーマット固有およびタイプ固有のフィールドは、オプションのトップレベルキーとして現れるのではなく、単一の
settingsオブジェクトの下にネストされます。formatとkindのフィールドは判別子として機能します。クライアントはこれらを使用してsettingsの形状を解釈します。 POSTとPATCHは、作成および更新操作に対して同じネストされた構造を受け取ります。
リポジトリの削除
DELETE /api/v1/:slug/repositories/:repository_name には、正確に true または false を値とする、破壊的操作の意図を示すクエリパラメータが 必須 です。このパラメータにデフォルト値はありません。パラメータを省略したリクエスト、またはそれ以外の値を指定したリクエストは 400 Bad Request を返します。この ADR が定めるのは契約であり、識別子ではありません。パラメータ名と詳細なセマンティクスは、仕様と実装で決定します。
| 値 | 動作 |
|---|---|
false | 空のリポジトリを削除して 204 を返す。ただし、仮想リポジトリがアップストリームとして参照している場合は 409 Conflict を返す。アーティファクトが残っているリポジトリでは 409 Conflict を返す |
true | カスケードしてリポジトリとその内容を削除する(すでに空の場合は 204、それ以外は 202)。ただし、仮想リポジトリがアップストリームとして参照している場合は 409 Conflict を返す |
空のリポジトリの削除は、仮想リポジトリがアップストリームとして参照している場合を除き、パラメータがどちらの値でも同期的に実行され、204 を返します。空でないリポジトリに対するカスケード削除は、仮想リポジトリがアップストリームとして参照している場合を除き、非同期です。リクエストは 202 Accepted を返し、リポジトリには直ちにアクセスできなくなります(削除が完了するまで名前は予約されたままです)。その後、バックグラウンドでストレージを回収します。
2 つの値は 2 段階の保護を提供します。呼び出し元が破壊的操作の意図を宣言し、さらに false なら内容には手を付けません。フラグを 1 つ誤っただけでは、呼び出し元が保持するつもりだったアーティファクトを消去できません。
このパラメータにまったく左右されない拒否条件が 1 つあります。いずれかの仮想リポジトリがアップストリームとして参照しているリポジトリは、どちらの値でも削除できず、リクエストは 409 Conflict を返します。この関連付けは、このリポジトリの内容ではなく別のリポジトリの設定であるため、破壊的操作の意図の対象にはなりません。呼び出し元は、まず DELETE .../upstream_repositories/:id を使ってアップストリームの関連付けを解除します。409 はこの条件を示しますが、参照元の仮想リポジトリは列挙しません。ネームスペースレベルで delete_repository を持つ呼び出し元は、ネームスペースのアップストリーム一覧も読み取れるため、参照元を見つけられます。
この説明だけでは、リポジトリにスコープされた Artifact Admin は参照元を把握できません。ADR-021 は権限の及ぶ範囲を割り当ての範囲に限定します。リポジトリへの割り当ては、そのリポジトリの delete_repository を付与しますが、そのリポジトリを参照する仮想リポジトリの read_repository は付与しません。その呼び出し元は詳細不明の 409 を受け取り、ネームスペースレベルの Artifact Admin に参照元の特定と関連付けの解除を依頼するか、すでに read_repository を持っている仮想リポジトリのアップストリーム一覧を確認します。
ADR-007 は、仮想アップストリームの中間テーブルに設定する NO ACTION 外部キーとして同じルールを定義しています。そのキーが最後の防御となり、呼び出し元にはこの 409 が返されます。この区別は重要です。参照アクションだけでは何も報告されないためです。削除文の実行タイミングに応じて、API レイヤーで変換する必要のある文の失敗になるか、呼び出し元には何も通知されないバックグラウンドの完全削除処理を中止します。
このパラメータを必須とすることで、パラメータが削除された際に明確に失敗するようにします。GA ではソフト削除が唯一の削除パスとなるため(ADR-010)、このパラメータは意味を失い、API から削除されます。それでもパラメータを送信するクローズドベータのインテグレーターには、一般的な不明フィールドの 400 ではなく、復元パスとしてごみ箱を明示した拒否を返します。この明示的な形式は、追加コストが小さい一方で、インテグレーターの移行に役立ちます。
統計:
GET /api/v1/:slug/statistics- 集約されたストレージとダウンロードの統計を取得GET /api/v1/:slug/:format/statistics- 特定のフォーマットのストレージとダウンロードの統計を取得
ライフサイクルポリシー:
GET /api/v1/:slug/lifecycle_policy- ライフサイクルポリシーを取得PATCH /api/v1/:slug/lifecycle_policy- ライフサイクルポリシーを更新GET /api/v1/:slug/lifecycle_policy/rules- ライフサイクルポリシールールを取得POST /api/v1/:slug/lifecycle_policy/rules- ライフサイクルポリシールールを作成GET /api/v1/:slug/lifecycle_policy/rules/:rule_id- ライフサイクルポリシールールを取得PATCH /api/v1/:slug/lifecycle_policy/rules/:rule_id- ライフサイクルポリシールールを更新DELETE /api/v1/:slug/lifecycle_policy/rules/:rule_id- ライフサイクルポリシールールを削除
ネームスペースの詳細:
GET /api/v1/:slug/namespace- このスコープが指定するネームスペースを取得
権限の判定結果は、既存の include_referrers パラメータにならった include_permissions ブール値によるオプトインで、ネームスペース、リポジトリ詳細、リポジトリ一覧のレスポンスに埋め込まれます。ADR-021 がセマンティクスを定義し、この ADR がルートとパラメータを確定します。
リポジトリレベル API
仮想リポジトリ - アップストリーム:
アップストリームは、フォーマット固有のアップストリームルールを持つフォーマットファミリーごとのテーブル(docker と oci で共有される container_virtual_repository_upstreams、および maven_virtual_repository_upstreams と npm_virtual_repository_upstreams)に保存されます。リモートとホスト型のリポジトリは独立したエンティティであるため、仮想リポジトリのアップストリームは既存のリポジトリへの参照です。アップストリームタイプ(ホスト型またはリモート)は、参照されるリポジトリの kind によって決定されます。
GET /api/v1/:slug/repositories/:repository_name/:format/upstream_repositories- 仮想リポジトリのアップストリームリポジトリ(ホスト型とリモート)を、解決の優先順位順にリストPOST /api/v1/:slug/repositories/:repository_name/:format/upstream_repositories- リポジトリ(ホスト型またはリモート)を仮想リポジトリのアップストリームとして関連付け。upstream_repository_idを受け取るGET /api/v1/:slug/repositories/:repository_name/:format/upstream_repositories/:id- アップストリームリポジトリの関連付けを取得PATCH /api/v1/:slug/repositories/:repository_name/:format/upstream_repositories/:id- 関連付けの位置を更新。positionフィールドのみ更新可能DELETE /api/v1/:slug/repositories/:repository_name/:format/upstream_repositories/:id- 仮想リポジトリからアップストリームの関連付けを解除
リモートリポジトリ - 接続テスト:
POST /api/v1/:slug/repositories/:repository_name/test- 構成されたリモートレジストリへの接続をテスト
統計:
GET /api/v1/:slug/repositories/:repository_name/statistics- リポジトリのストレージとダウンロードの統計を取得
ライフサイクルポリシー:
リポジトリレベルのライフサイクルポリシーは、ネームスペースレベルのデフォルトを上書きするフォーマットファミリーごとのテーブル(container_repository_lifecycle_policy_settings など)を使用します。
GET /api/v1/:slug/repositories/:repository_name/:format/lifecycle_policy- リポジトリのライフサイクルポリシーを取得PATCH /api/v1/:slug/repositories/:repository_name/:format/lifecycle_policy- リポジトリのライフサイクルポリシーを更新GET /api/v1/:slug/repositories/:repository_name/:format/lifecycle_policy/rules- リポジトリのライフサイクルポリシールールを取得POST /api/v1/:slug/repositories/:repository_name/:format/lifecycle_policy/rules- リポジトリのライフサイクルポリシールールを作成GET /api/v1/:slug/repositories/:repository_name/:format/lifecycle_policy/rules/:rule_id- ライフサイクルポリシールールを取得PATCH /api/v1/:slug/repositories/:repository_name/:format/lifecycle_policy/rules/:rule_id- ライフサイクルポリシールールを更新DELETE /api/v1/:slug/repositories/:repository_name/:format/lifecycle_policy/rules/:rule_id- ライフサイクルポリシールールを削除
フォーマット固有のアーティファクト API
すべてのアーティファクトエンドポイントはリポジトリにスコープされ、ホスト型とリモートの両方のリポジトリに一様に適用されます。ルート、動詞、ページネーションは両方の種類で同一です。リモートリポジトリは、ホスト型スキーマをミラーリングする階層テーブル(*_remote_images、*_remote_packages、*_remote_versions、*_remote_files など)にアーティファクトをキャッシュします(ADR-007 を参照)。
レスポンスボディはリポジトリの kind によってポリモーフィックです。これは リポジトリ詳細の settings オブジェクト で使用されるのと同じ慣例です。kind が remote の場合、鮮度追跡された行(タグ、パッケージファイル、メタデータファイル)にマッピングされる各エントリは、upstream_checked_at と upstream_etag を公開するネストされた cache オブジェクトを持ちます。コンテナのマニフェストと blob はダイジェストによってコンテンツアドレス指定され、行ごとの鮮度を持たないため、cache ブロックを持ちません。リポジトリレベルのキャッシュ構成(cache_validity_hours、metadata_cache_validity_hours)はリポジトリ詳細の settings オブジェクトに存在し、アーティファクトごとには反映されません。ホスト型リポジトリに対するリクエストは、cache オブジェクト なし で同じ形状を返します。
リモートリポジトリ上の動詞のセマンティクスは、アップストリームではなくキャッシュされた行を記述します。
DELETEはキャッシュされた行を退避します(アップストリームへの影響なし。アーティファクトはクライアント API を通じた次の pull で再取得されます)。PATCH .../quarantineはキャッシュされた行をブロック済みとしてフラグします。アップストリームにまだアーティファクトがあっても、クライアントの pull は404 Not Foundを返します。フラグはキャッシュされた行のライフサイクルに紐付けられ、退避するとクリアされます。永続的またはダイジェストレベルのブロックは意図的にスコープ外です(API ではなくライフサイクル管理の関心事)。GETは、上記のcacheサブオブジェクトを除いて両方の種類で同一です。
コンテナファミリー固有 - イメージ:
コンテナファミリーのリポジトリでは、アーティファクトを「イメージ」と呼びます。これらのルートの :format セグメントは、リポジトリのフォーマットである docker または oci です:
GET /api/v1/:slug/repositories/:repository_name/:format/images- リポジトリ内のイメージをリストGET /api/v1/:slug/repositories/:repository_name/:format/images/:image_id- イメージの詳細を取得GET /api/v1/:slug/repositories/:repository_name/:format/images/:image_id/manifests- 所定のイメージのマニフェストをリストGET /api/v1/:slug/repositories/:repository_name/:format/images/:image_id/blobs- 所定のイメージの blob をリストGET /api/v1/:slug/repositories/:repository_name/:format/images/:image_id/statistics- イメージのストレージ、使用量、ダウンロードの統計を取得DELETE /api/v1/:slug/repositories/:repository_name/:format/images/:image_id- イメージを削除(ソフトまたはハード削除)DELETE /api/v1/:slug/repositories/:repository_name/:format/images- イメージを一括削除PATCH /api/v1/:slug/repositories/:repository_name/:format/images/:image_id/quarantine- 所定のイメージを隔離
コンテナファミリー固有 - イメージタグ:
GET /api/v1/:slug/repositories/:repository_name/:format/images/:image_id/tags- 所定のイメージのタグをリストGET /api/v1/:slug/repositories/:repository_name/:format/images/:image_id/tags/:tag/statistics- タグのストレージ、使用量、ダウンロードの統計を取得DELETE /api/v1/:slug/repositories/:repository_name/:format/images/:image_id/tags/:tag- イメージタグを削除DELETE /api/v1/:slug/repositories/:repository_name/:format/images/:image_id/tags- 一連のイメージタグを削除
Maven/NPM 固有 - パッケージ:
GET /api/v1/:slug/repositories/:repository_name/:format/packages- リポジトリ内のパッケージをリストGET /api/v1/:slug/repositories/:repository_name/:format/packages/:package_id- パッケージの詳細を取得GET /api/v1/:slug/repositories/:repository_name/:format/packages/:package_id/statistics- パッケージのストレージ、使用量、ダウンロードの統計を取得DELETE /api/v1/:slug/repositories/:repository_name/:format/packages/:package_id- パッケージを削除(ソフトまたはハード削除)DELETE /api/v1/:slug/repositories/:repository_name/:format/packages- パッケージを一括削除PATCH /api/v1/:slug/repositories/:repository_name/:format/packages/:package_id/quarantine- 所定のパッケージを隔離GET /api/v1/:slug/repositories/:repository_name/:format/packages/:package_id/versions- 所定のパッケージのバージョンをリスト
Maven/NPM 固有 - パッケージバージョン:
GET /api/v1/:slug/repositories/:repository_name/:format/versions/:version_id- バージョンの詳細を取得GET /api/v1/:slug/repositories/:repository_name/:format/versions/:version_id/statistics- パッケージバージョンのストレージ、使用量、ダウンロードの統計を取得DELETE /api/v1/:slug/repositories/:repository_name/:format/versions/:version_id- バージョンを削除(ソフトまたはハード削除)DELETE /api/v1/:slug/repositories/:repository_name/:format/versions- バージョンを一括削除GET /api/v1/:slug/repositories/:repository_name/:format/versions/:version_id/files- 所定のバージョンのファイルをリスト
Maven/NPM 固有 - パッケージファイル:
GET /api/v1/:slug/repositories/:repository_name/:format/files/:file_id- ファイルの詳細を取得GET /api/v1/:slug/repositories/:repository_name/:format/files/:file_id/download- ファイルをダウンロードDELETE /api/v1/:slug/repositories/:repository_name/:format/files/:file_id- ファイルを削除(ソフトまたはハード削除)DELETE /api/v1/:slug/repositories/:repository_name/:format/files- ファイルを一括削除
NPM 固有 - 配布タグ:
GET /api/v1/:slug/repositories/:repository_name/npm/packages/:package_id/tags- 所定のパッケージのタグをリストGET /api/v1/:slug/repositories/:repository_name/npm/tags/:tag_id- タグの詳細を取得GET /api/v1/:slug/repositories/:repository_name/npm/tags/:tag_id/statistics- タグのストレージ、使用量、ダウンロードの統計を取得DELETE /api/v1/:slug/repositories/:repository_name/npm/tags/:tag_id- タグを削除
アーティファクト管理クライアント API
クライアント API の URL は、すべてのリポジトリタイプ(ホスト型、リモート、仮想)で同じです。レジストリはリポジトリの種類を内部的に解決し、タイプ固有の挙動を適用します(例:リモートおよび仮想リポジトリへの書き込みを拒否する、現時点でリポジトリの種類が提供できない読み取りを拒否する)。
Container
OCI Distribution Spec v1.1 を実装します。認証:Bearer トークン。
以下のリテラルの container パスセグメントはプロトコルレベルで固定されています。1 セットの /v2 エンドポイントが、docker と oci の両フォーマットのリポジトリを提供します。これはルーターが挿入するリテラルであり、リポジトリの format 値ではありません(管理 API を参照)。
GET /v2/- API バージョンとレジストリ実装を確認(OCI 必須、スラッグにスコープされない)GET /v2/:slug/container/:repository_name/:image_name/manifests/:reference- マニフェストを取得(reference はタグまたはダイジェスト)HEAD /v2/:slug/container/:repository_name/:image_name/manifests/:reference- マニフェストの存在を確認PUT /v2/:slug/container/:repository_name/:image_name/manifests/:reference- マニフェストをアップロード(リモートおよび仮想リポジトリでは利用不可)DELETE /v2/:slug/container/:repository_name/:image_name/manifests/:reference- マニフェストを削除(ダイジェストまたはタグ参照による、リモートおよび仮想リポジトリでは利用不可)DELETE /v2/:slug/container/:repository_name/:image_name/manifests/:tag- 特定のタグを削除(リモートおよび仮想リポジトリでは利用不可)GET /v2/:slug/container/:repository_name/:image_name/blobs/:digest- blob をダウンロードHEAD /v2/:slug/container/:repository_name/:image_name/blobs/:digest- blob の存在を確認DELETE /v2/:slug/container/:repository_name/:image_name/blobs/:digest- blob を削除(リモートおよび仮想リポジトリでは利用不可)POST /v2/:slug/container/:repository_name/:image_name/blobs/uploads/- blob アップロードを開始(リモートおよび仮想リポジトリでは利用不可)PATCH /v2/:slug/container/:repository_name/:image_name/blobs/uploads/:uuid- blob チャンクをアップロード(リモートおよび仮想リポジトリでは利用不可)GET /v2/:slug/container/:repository_name/:image_name/blobs/uploads/:uuid- blob アップロードのステータスを取得(再開可能なアップロード用、リモートおよび仮想リポジトリでは利用不可)PUT /v2/:slug/container/:repository_name/:image_name/blobs/uploads/:uuid?digest=:digest- blob アップロードを完了(リモートおよび仮想リポジトリでは利用不可)DELETE /v2/:slug/container/:repository_name/:image_name/blobs/uploads/:uuid- blob アップロードをキャンセル(リモートおよび仮想リポジトリでは利用不可)POST /v2/:slug/container/:repository_name/:image_name/blobs/uploads/?digest=:digest- 単一リクエストで完全な blob をアップロード(リモートおよび仮想リポジトリでは利用不可)GET /v2/:slug/container/:repository_name/:image_name/tags/list- リポジトリ内のすべてのタグをリスト(MVP の仮想リポジトリでは利用不可)GET /v2/:slug/container/:repository_name/:image_name/tags/list?n=100&last=tag_name- ページネーションされたタグのリスト(MVP の仮想リポジトリでは利用不可)GET /v2/:slug/container/:repository_name/:image_name/referrers/:digest- マニフェストを参照するアーティファクト/アテステーションをリスト(MVP の仮想リポジトリでは利用不可)GET /v2/:slug/container/:repository_name/:image_name/referrers/:digest?artifactType=<type>- アーティファクトタイプで referrer をフィルタリング(MVP の仮想リポジトリでは利用不可)
注: tags/list と referrers/:digest が利用できないのは、仮想コンテナリポジトリのみです。リモートリポジトリでは、どちらもアップストリームへのライブプロキシとして提供されます。仮想リポジトリは、書き込み動詞に対する 405 ではなく、どちらにも 404 NAME_UNKNOWN で応答します。書き込みは存在するエンドポイントを対象とするものの、そのメソッドが利用できないのに対し、仮想リストには、現在の解決モデルで生成できる単一のコレクションが存在しないためです。referrers/:digest では、404 によって 一部の OCI クライアントが referrer タグスキーマへフォールバックすることも可能になります。これは、他のタグと同様に仮想リポジトリのアップストリーム全体で解決されるため、referrer の検出は完全に失敗せず、機能を落とした形で継続できます。一方、空のリストを含む 200 は、どのアップストリームからも根拠が得られていないのに、referrer が存在しないとクライアントに伝えてしまいます。tags/list には、どのコードでも同等のフォールバックはありません。
どのクライアントがフォールバックするかはエラーコードによって決まり、その分岐点が NAME_UNKNOWN です。crane はエラーコードをまったく読み取りません。404、400、406、または Content-Type が OCI index ではない 200 でフォールバックし、それ以外のステータスはエラーとして返します。oras は本文を解析し、コードが NAME_UNKNOWN の場合はエラーを返します。同じ 404 でも他のコードなら、タグスキーマへ移行します。Notation は oras-go を介してその挙動を継承します。これは go-containerregistry v0.20.6、oras-go v2.5.0 と v2.6.0、および oras-go v2.5.0 に固定された notation-go v1.3.2 を対象に測定しました。
OCI のエラーセットには、「このリポジトリは存在するが、リストできるコレクションがない」ことを示すコードがありません。そのため、ここでは NAME_UNKNOWN を積極的に選んだのではなく再利用しており、2 つのコストが伴います。検出ツールはこれを「そのようなリポジトリはない」と解釈するため、同じ URL への pull は成功する一方、crane ls、skopeo list-tags、レジストリ UI は仮想リポジトリが存在しないと報告します。また、空の 200 より 404 を優先する動機となるフォールバックは、まさにコードを読み取るクライアントでは利用できません。
これはアーキテクチャ上の制約ではなく、MVP の制限です。アップストリーム全体から単一のパスを解決する方法は定義されていますが、独立したカーソルを持つアップストリームをまたいでページネーションされたコレクションをマージする方法は定義されていません。アップストリーム横断のタグ一覧と referrer は artifact-registry#264 で追跡されます。そのスコープは、暫定的に含まれるコードではなく、404 を置き換えるマージです。コード自体は artifact-registry#1019 で追跡されます。NAME_UNKNOWN 以外のコードを含む 404 なら、crane だけでなく oras と Notation でもタグスキーマへのフォールバックが機能し続けます。
注: OCI 必須の GET /v2/ エンドポイントは /:slug プレフィックスを含まないため、Cells ルーターはパスだけからどの Cell がリクエストを処理すべきかを判断できません。GET /v2/ はステートレスなバージョンプローブ(OCI 準拠を示す 200 OK、そうでなければ 401 Unauthorized)であるため、どの Cell でも提供できます。スラッグやルーティングコンテキストは不要です。他のすべてのクライアントリクエストは /:slug セグメントを持ち、Cells ルーターがそれを使用してターゲット Cell を判断します。クライアントは glab を介してクライアント側で認証情報を取得し(ADR-020 を参照)、最初から Bearer トークンを提示するため、OCI の 401 WWW-Authenticate リダイレクトチャレンジは使用されません。GET /v2/_catalog(Docker Registry HTTP API V2)は OCI Distribution Spec の一部ではなく、実装されません。
クライアント設定例
スラッグ acme-engineering で、リポジトリ名 my-repo、イメージ名 my-app、タグ latest のコンテナファミリーのリポジトリからイメージを pull する場合:
docker pull artifact-registry.gitlab.com/acme-engineering/container/my-repo/my-app:latest
Maven
Maven Repository Layout を実装します。認証:Basic 認証。カスタムヘッダー認証(元の GitLab Maven パッケージレジストリからのレガシーメカニズム)はサポートされません。Basic 認証は Maven レジストリ全体で普遍的な標準であり、すべての主要なビルドツール(mvn、gradle、sbt)でサポートされています。特に sbt は Basic 認証のみをサポートします。
GET /:slug/maven/:repository_name/*path/:file_name- Maven のホスト型、リモート、仮想リポジトリからパッケージファイルをダウンロードPUT /:slug/maven/:repository_name/*path/:file_name- Maven のホスト型リポジトリにパッケージファイルをアップロード(リモートおよび仮想リポジトリでは利用不可)
クライアント設定例
スラッグ acme-engineering の my-repo という名前の Maven リポジトリのリポジトリ URL。settings.xml、build.gradle、または build.sbt で使用します:
https://artifact-registry.gitlab.com/acme-engineering/maven/my-repo
NPM
NPM Registry API を実装します。認証:Bearer トークン。
GET /:slug/npm/:repository_name/:package_name- パッケージメタデータを取得PUT /:slug/npm/:repository_name/:package_name- パッケージを公開(リモートおよび仮想リポジトリでは利用不可)PUT /:slug/npm/:repository_name/:package_name/-rev/:rev- 単一バージョンの公開取り消し、ステップ 1:そのバージョンを削除したパッケージドキュメントに置き換える(リモートおよび仮想リポジトリでは利用不可)DELETE /:slug/npm/:repository_name/:package_name/-/:file_name/-rev/:rev- 単一バージョンの公開取り消し、ステップ 2:バージョンの tarball を削除(リモートおよび仮想リポジトリでは利用不可)DELETE /:slug/npm/:repository_name/:package_name/-rev/:rev- パッケージ全体の公開取り消し(npm unpublish <pkg> --force、リモートおよび仮想リポジトリでは利用不可)GET /:slug/npm/:repository_name/:package_name/-/:file_name- パッケージファイルをダウンロードGET /:slug/npm/:repository_name/-/package/:package_name/dist-tags- パッケージの dist-tags をリストPUT /:slug/npm/:repository_name/-/package/:package_name/dist-tags/:tag- dist-tag を作成または更新(リモートおよび仮想リポジトリでは利用不可)DELETE /:slug/npm/:repository_name/-/package/:package_name/dist-tags/:tag- dist-tag を削除(リモートおよび仮想リポジトリでは利用不可)POST /:slug/npm/:repository_name/-/npm/v1/security/audits/quick- クイックセキュリティ監査POST /:slug/npm/:repository_name/-/npm/v1/security/advisories/bulk- 一括セキュリティアドバイザリ
クライアント設定例
スラッグ acme-engineering の my-repo という名前の NPM リポジトリのレジストリ URL。.npmrc で使用します:
https://artifact-registry.gitlab.com/acme-engineering/npm/my-repo/
GitLab API
GitLab API は、最初は Rails モノリスであるプラットフォーム呼び出し元にサービスを提供します。リクエストは呼び出すサービスを認証し、エンドユーザーを認証することはありません。ユーザーのチェックは呼び出しをトリガーするプラットフォームフローに属し、ユーザー向けトラフィックは代わりに管理 API とクライアント API を使用します(ADR-014、ADR-021)。「GitLab」は呼び出し元の名前です。つまり GitLab プラットフォームデプロイメントであり、ハイブリッドデプロイメントモデルではお客様がホストするモノリスです。そのためこのサーフェスはインターネット向けですが、お客様向けではありません。
ハイブリッドデプロイメントモデル(SaaS レジストリに対する Self-Managed または Dedicated モノリス)を含め、このサーフェスで呼び出し元を認証する方法は、ADR-020 と ADR-021 で扱います(artifact-registry#255)。
ネームスペースのライフサイクル
これらのエンドポイントにはスラッグが不要です。プロビジョニングはスラッグが取得される前に実行され、解決は UUID をスラッグに変換し、サービス条件はスラッグの状態にかかわらず機能する必要があるためです。ネームスペースを所有者アンカー(ADR-001)または UUID(ADR-022)で参照します。
POST /api/gitlab/v1/namespaces- 所有者アンカーのネームスペースをプロビジョニングします(gitlab#603023)。アンカーに対して冪等であり、呼び出し元が永続化するネームスペース UUID を返します(ADR-022)。GET /api/gitlab/v1/namespaces/:uuid- UUID でネームスペースを解決します。所有者アンカー、スラッグ、ステータスを返します。呼び出し元はスラッグとステータスをキャッシュします(ADR-022、ADR-014)。POST /api/gitlab/v1/namespaces/:uuid/<action>- 可逆的なサービス条件を適用または解除します(ADR-007)。アクションごとに 1 つのエンドポイントを設け、<action>はblock、unblock、disable、enable、suspend、unsuspendのいずれかです。
スラッグが不要なルートは Cells ルーティングを弱めません。ネームスペースの作成はキーでルーティングできません。まだルーティングの対象となるものが存在しないためです。Cell に存在するモノリスはその Cell ローカルのレジストリを直接呼び出し、ネームスペースはその Cell に配置されます。ハイブリッドの呼び出し元には Cell がないため、その呼び出しはエッジに入り、エッジが対象 Cell を決定します。どちらのパスもネームスペース UUID をルーティングキーとして登録します(スラッグの取得時には第 2 のキーとして追加されます)。他のすべての GitLab API エンドポイントはその UUID をキーとし、呼び出し元の場所にかかわらず、スラッグプレフィックス付きリクエストと同じトポロジー機構を経由してルーティングされます。UUID をキーにしたルーティングが十分でないと判明した場合、Container Registry で以前に検討されたように、GitLab API リクエストは所有者アンカーをルーティングヒントとして追加で埋め込むこともできます。これは潜在的な経路であり、現時点の要件ではありません。
リソース検証
プラットフォームの認可フローは、一括ロール付与の対象リソースがネームスペースに属することを確認しなければなりません(artifact-registry#181)。ネームスペース対象の付与にはレジストリ呼び出しは不要です。プラットフォームが永続化されたマッピングに対して所有権を検証します(gitlab#603023)。リポジトリ対象の付与は、ネームスペース UUID にスコープされ、単一の HASH(namespace_id) パーティションから提供されるバッチ検証を使用します。
POST /api/gitlab/v1/namespaces/:uuid/repositories/verifications- 指定されたリポジトリ ID がネームスペース配下に存在することを一括検証します。すべての ID が属する場合は本文なしの204を返します。ID が不明、またはネームスペース外の場合は、失敗した ID だけをerror.details.repository_idsに列挙した422を返します(重複を除き、送信順を維持)。ただし ID が失敗した理由は示さず、どこにも存在しない ID と別のネームスペースが所有する ID は同じ応答になります。不正なバッチ(空、サイズ超過、または非正規形式の ID)には400を返します。
レジストリは Organization データを返しません。呼び出し元はネームスペーススコープを提供し、その所有者をすでに把握しています。将来の割り当て可能なリソースタイプは、同じタイプ別のパターン(POST /api/gitlab/v1/namespaces/:uuid/<resource_type>/verifications)に従います。
GitLab API は、提供先のプラットフォームバージョン間で後方互換性を維持しなければなりません。ハイブリッドデプロイメントモデルでは、Self-Managed モノリスは SaaS レジストリより遅れることがあります(ADR-022)。
帰結
ポジティブ
- 明確な関心の分離:3 つの API サーフェスは明確な目的、認証メカニズム、ターゲットオーディエンスを持ち、混乱を減らし、独立した進化を可能にします
- リポジトリにアンカーされた URL パターン:すべてのアーティファクト操作はリポジトリにスコープされ、明確なルーティングコンテキストを提供し、ホスト型とリモートの両方のリポジトリに対して同じ URL 構造を可能にします
- 永続的に安定した、人間が読める URL:スラッグとリポジトリ名はどちらも不変であるため、すべての URL パスセグメントは人間が読めて決して変わりません。クライアント向けのどの URL にも数値 ID は現れません
- 統合されたハイブリッドリスト:単一のエンドポイントが、すべてのフォーマットにわたるすべてのリポジトリ(ホスト型、仮想、リモート)をフィルタリング付きでリストし、プラットフォームエンジニア向けのフォーマット横断のガバナンスと監査性のビューを可能にします
- 独立したエンティティとしてのリモートリポジトリ:リモートリポジトリは個別に管理可能で、複数の仮想リポジトリにまたがって再利用でき、独自のライフサイクルを持ち、業界の慣行(JFrog Artifactory、Sonatype Nexus、Google Cloud AR)に一致します
- 一様なホスト型とリモートのアーティファクト構造:リモートリポジトリはホスト型リポジトリと同じアーティファクト階層(images、packages、versions、files)を使用します。同じルートが両方の種類を提供し、それを追跡する行にネストされた
cacheサブオブジェクトが鮮度メタデータ(upstream_checked_at、upstream_etag)を表面化します。キャッシュの変更はホスト型リポジトリと同じ動詞を再利用するため(DELETE= 退避、PATCH .../quarantine= ブロック)、リモートリポジトリに対して API の表面は拡大しません。レスポンスボディのみがポリモーフィックです - 簡素化されたアップストリームモデル:仮想リポジトリのアップストリームは、アップストリームタイプごとに別個のエンドポイント階層を使用するのではなく、ID で既存のリポジトリを参照し、API の表面と実装の複雑さを減らします
- ADR 外でのスキーマ進化:リクエストおよびレスポンスのペイロードは Artifact Registry コードベースの OpenAPI 仕様に存在するため、ペイロードの変更で ADR を更新する必要はありません
- 将来の拡張性:この設計パターンは、アーキテクチャの変更なしに追加のパッケージフォーマット(PyPI、NuGet、Helm など)に容易に対応します。管理 API とクライアント API は、それぞれの要件に基づいて独立して進化できます
ネガティブ
- リポジトリ名の不変性が柔軟性を制限する:タイプミスや組織的なリネームには、単にリネームするのではなく、新しいリポジトリを作成してアーティファクトを移行することが必要になります
- グローバルな名前の一意性が制限的:
my-appのような名前はフォーマットをまたいで再利用できません(例:my-appという名前の Docker と Maven の両方のリポジトリ)。これはmy-app-dockerやmy-app-mavenのような命名規則につながる可能性があります。この制約は後で破壊的な変更なしに緩和できます - フォーマット固有の API 表面:フォーマットごとにエンドポイントを専用化することは、フォーマットをまたいだ一部の重複と、管理タスクに対する統合されたフォーマット横断の操作がないことを意味します
- 仮想コンテナリポジトリが検出ツールに存在しないものとして報告される:そこでは
tags/listとreferrers/:digestが404 NAME_UNKNOWNを返し、同じ URL への pull は成功する一方、crane ls、skopeo list-tags、レジストリ UI は「そのようなリポジトリはない」と解釈します。コンテナルートの注記に、その理由とコードによって referrer のフォールバックに生じるコストを記載しています
参考文献
- ADR-001:アンカーポイントとしての組織
- ADR-010:データ保持 - クローズドベータから GA までの削除の段階的導入
- ADR-022:ネームスペースデカップリング
- Cells アーキテクチャ
- Container Registry Routing Service (Cells)
- GitLab REST API 認証
- OCI Distribution Spec v1.1
- Docker Registry Bearer Token Authentication
- Maven Repository Layout
- NPM Registry API
- Repository Name Immutability Proposal
a1f3c26a)