Commit a4011606 authored by Stan Hu's avatar Stan Hu

Support Docker OCI images

Docker Distribution v2.7.0 shipped with OCI support, but our container
registry client was not updated to handle the manifest format in the
HTTP `Accept` header.  As a result, API calls to retrieve a manifest
would return with an error, "OCI manifest found, but accept header does
not support OCI manifests". This would result in blank fields in the
container registry page and prevent tags from being deleted.

To fix this, we just need to add
`application/vnd.oci.image.manifest.v1+json` to the `Accept` header and
configure Faraday to parse the response as JSON. The response structure
is the same as the standard Docker Distribution V2 manifest.

Closes https://gitlab.com/gitlab-org/gitlab-ce/issues/58685

Closes https://gitlab.com/gitlab-org/gitlab-ee/issues/12877
parent a5eaefc9
---
title: Support Docker OCI images
merge_request: 31127
author:
type: fixed
...@@ -7,7 +7,9 @@ module ContainerRegistry ...@@ -7,7 +7,9 @@ module ContainerRegistry
class Client class Client
attr_accessor :uri attr_accessor :uri
MANIFEST_VERSION = 'application/vnd.docker.distribution.manifest.v2+json'.freeze DOCKER_DISTRIBUTION_MANIFEST_V2_TYPE = 'application/vnd.docker.distribution.manifest.v2+json'
OCI_MANIFEST_V1_TYPE = 'application/vnd.oci.image.manifest.v1+json'
ACCEPTED_TYPES = [DOCKER_DISTRIBUTION_MANIFEST_V2_TYPE, OCI_MANIFEST_V1_TYPE].freeze
# Taken from: FaradayMiddleware::FollowRedirects # Taken from: FaradayMiddleware::FollowRedirects
REDIRECT_CODES = Set.new [301, 302, 303, 307] REDIRECT_CODES = Set.new [301, 302, 303, 307]
...@@ -60,12 +62,13 @@ module ContainerRegistry ...@@ -60,12 +62,13 @@ module ContainerRegistry
end end
def accept_manifest(conn) def accept_manifest(conn)
conn.headers['Accept'] = MANIFEST_VERSION conn.headers['Accept'] = ACCEPTED_TYPES
conn.response :json, content_type: 'application/json' conn.response :json, content_type: 'application/json'
conn.response :json, content_type: 'application/vnd.docker.distribution.manifest.v1+prettyjws' conn.response :json, content_type: 'application/vnd.docker.distribution.manifest.v1+prettyjws'
conn.response :json, content_type: 'application/vnd.docker.distribution.manifest.v1+json' conn.response :json, content_type: 'application/vnd.docker.distribution.manifest.v1+json'
conn.response :json, content_type: 'application/vnd.docker.distribution.manifest.v2+json' conn.response :json, content_type: DOCKER_DISTRIBUTION_MANIFEST_V2_TYPE
conn.response :json, content_type: OCI_MANIFEST_V1_TYPE
end end
def response_body(response, allow_redirect: false) def response_body(response, allow_redirect: false)
......
...@@ -6,6 +6,42 @@ describe ContainerRegistry::Client do ...@@ -6,6 +6,42 @@ describe ContainerRegistry::Client do
let(:options) { { token: token } } let(:options) { { token: token } }
let(:client) { described_class.new("http://container-registry", options) } let(:client) { described_class.new("http://container-registry", options) }
shared_examples '#repository_manifest' do |manifest_type|
let(:manifest) do
{
"schemaVersion" => 2,
"config" => {
"mediaType" => manifest_type,
"digest" =>
"sha256:4a3ef0786dd241be6000311e1503869b320be433b9cba84cfafeb512d1720c95",
"size" => 6608
},
"layers" => [
{
"mediaType" => manifest_type,
"digest" =>
"sha256:83ef92b73cf4595aa7fe214ec6747228283d585f373d8f6bc08d66bebab531b7",
"size" => 2828661
}
]
}
end
it 'GET /v2/:name/manifests/mytag' do
stub_request(:get, "http://container-registry/v2/group/test/manifests/mytag")
.with(headers: {
'Accept' => described_class::ACCEPTED_TYPES.join(', '),
'Authorization' => "bearer #{token}"
})
.to_return(status: 200, body: manifest.to_json, headers: { content_type: manifest_type })
expect(client.repository_manifest('group/test', 'mytag')).to eq(manifest)
end
end
it_behaves_like '#repository_manifest', described_class::DOCKER_DISTRIBUTION_MANIFEST_V2_TYPE
it_behaves_like '#repository_manifest', described_class::OCI_MANIFEST_V1_TYPE
describe '#blob' do describe '#blob' do
it 'GET /v2/:name/blobs/:digest' do it 'GET /v2/:name/blobs/:digest' do
stub_request(:get, "http://container-registry/v2/group/test/blobs/sha256:0123456789012345") stub_request(:get, "http://container-registry/v2/group/test/blobs/sha256:0123456789012345")
......
...@@ -9,7 +9,7 @@ describe ContainerRegistry::Tag do ...@@ -9,7 +9,7 @@ describe ContainerRegistry::Tag do
end end
let(:headers) do let(:headers) do
{ 'Accept' => 'application/vnd.docker.distribution.manifest.v2+json' } { 'Accept' => ContainerRegistry::Client::ACCEPTED_TYPES.join(', ') }
end end
let(:tag) { described_class.new(repository, 'tag') } let(:tag) { described_class.new(repository, 'tag') }
......
...@@ -16,7 +16,7 @@ describe ContainerRepository do ...@@ -16,7 +16,7 @@ describe ContainerRepository do
host_port: 'registry.gitlab') host_port: 'registry.gitlab')
stub_request(:get, 'http://registry.gitlab/v2/group/test/my_image/tags/list') stub_request(:get, 'http://registry.gitlab/v2/group/test/my_image/tags/list')
.with(headers: { 'Accept' => 'application/vnd.docker.distribution.manifest.v2+json' }) .with(headers: { 'Accept' => ContainerRegistry::Client::ACCEPTED_TYPES.join(', ') })
.to_return( .to_return(
status: 200, status: 200,
body: JSON.dump(tags: ['test_tag']), body: JSON.dump(tags: ['test_tag']),
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment