Commit 97f85fd6 authored by Douglas Barbosa Alexandre's avatar Douglas Barbosa Alexandre

Merge branch 'fixes_for_regostry_replication' into 'master'

Fixes for Docker Registry Replication

See merge request gitlab-org/gitlab!16196
parents 969f73b8 1b0d65c2
...@@ -35,9 +35,10 @@ module Geo ...@@ -35,9 +35,10 @@ module Geo
def sync_tag(tag) def sync_tag(tag)
file = nil file = nil
manifest = client.repository_manifest(name, tag) manifest = client.repository_raw_manifest(name, tag)
manifest_parsed = JSON.parse(manifest)
list_blobs(manifest).each do |digest| list_blobs(manifest_parsed).each do |digest|
next if container_repository.blob_exists?(digest) next if container_repository.blob_exists?(digest)
file = client.pull_blob(name, digest) file = client.pull_blob(name, digest)
...@@ -45,7 +46,7 @@ module Geo ...@@ -45,7 +46,7 @@ module Geo
file.unlink file.unlink
end end
container_repository.push_manifest(tag, manifest, manifest['mediaType']) container_repository.push_manifest(tag, manifest, manifest_parsed['mediaType'])
ensure ensure
file.try(:unlink) file.try(:unlink)
end end
......
...@@ -3,6 +3,8 @@ ...@@ -3,6 +3,8 @@
module EE module EE
module ContainerRegistry module ContainerRegistry
module Client module Client
include ::Gitlab::Utils::StrongMemoize
Error = Class.new(StandardError) Error = Class.new(StandardError)
# In the future we may want to read a small chunks into memory and use chunked upload # In the future we may want to read a small chunks into memory and use chunked upload
...@@ -12,7 +14,7 @@ module EE ...@@ -12,7 +14,7 @@ module EE
url = get_upload_url(name, digest) url = get_upload_url(name, digest)
headers = { 'Content-Type' => 'application/octet-stream', 'Content-Length' => payload.size.to_s } headers = { 'Content-Type' => 'application/octet-stream', 'Content-Length' => payload.size.to_s }
response = faraday_upload.put(url, payload, headers) response = faraday.put(url, payload, headers)
raise Error.new("Push Blob error: #{response.body}") unless response.success? raise Error.new("Push Blob error: #{response.body}") unless response.success?
...@@ -51,6 +53,10 @@ module EE ...@@ -51,6 +53,10 @@ module EE
file.close file.close
end end
def repository_raw_manifest(name, reference)
response_body faraday_raw.get("/v2/#{name}/manifests/#{reference}")
end
private private
def get_upload_url(name, digest) def get_upload_url(name, digest)
...@@ -58,21 +64,24 @@ module EE ...@@ -58,21 +64,24 @@ module EE
raise Error.new("Get upload URL error: #{response.body}") unless response.success? raise Error.new("Get upload URL error: #{response.body}") unless response.success?
response.headers['location']
upload_url = URI(response.headers['location']) upload_url = URI(response.headers['location'])
upload_url.query = "#{upload_url.query}&#{URI.encode_www_form(digest: digest)}" upload_url.query = "#{upload_url.query}&#{URI.encode_www_form(digest: digest)}"
upload_url upload_url
end end
def faraday_upload # rubocop:disable Gitlab/ModuleWithInstanceVariables
@faraday_upload ||= Faraday.new(@base_uri) do |conn| # rubocop:disable Gitlab/ModuleWithInstanceVariables def faraday_raw
initialize_connection(conn, @options) # rubocop:disable Gitlab/ModuleWithInstanceVariables strong_memoize(:faraday_raw) do
conn.request :multipart Faraday.new(@base_uri) do |conn|
conn.request :url_encoded initialize_connection(conn, @options, &method(:accept_raw_manifest))
conn.adapter :net_http end
end end
end end
# rubocop:enable Gitlab/ModuleWithInstanceVariables
def accept_raw_manifest(conn)
conn.headers['Accept'] = ::ContainerRegistry::Client::ACCEPTED_TYPES
end
end end
end end
end end
...@@ -121,4 +121,20 @@ describe ContainerRegistry::Client do ...@@ -121,4 +121,20 @@ describe ContainerRegistry::Client do
expect(client.blob_exists?('group/test', digest)).to eq(false) expect(client.blob_exists?('group/test', digest)).to eq(false)
end end
end end
describe '#repository_raw_manifest' do
let(:manifest) { '{schemaVersion: 2, layers:[]}' }
it 'GET "/v2/:name/manifests/:reference' do
stub_request(:get, 'http://registry/v2/group/test/manifests/my-tag')
.with(
headers: {
'Accept' => 'application/vnd.docker.distribution.manifest.v2+json, application/vnd.oci.image.manifest.v1+json',
'Authorization' => 'bearer 12345'
})
.to_return(status: 200, body: manifest, headers: {})
expect(client.repository_raw_manifest('group/test', 'my-tag')).to eq(manifest)
end
end
end end
...@@ -10,6 +10,11 @@ describe Geo::ContainerRepositorySync, :geo do ...@@ -10,6 +10,11 @@ describe Geo::ContainerRepositorySync, :geo do
create(:container_repository, name: 'my_image', project: project) create(:container_repository, name: 'my_image', project: project)
end end
# Break symbol will be removed if JSON encode/decode operation happens
# so we use this to prove that it does not happen and we preserve original
# human readable JSON
let(:manifest) { "{\"schemaVersion\":2,\n\"layers\":[]}" }
before do before do
stub_container_registry_config(enabled: true, stub_container_registry_config(enabled: true,
api_url: 'http://registry.gitlab', api_url: 'http://registry.gitlab',
...@@ -52,12 +57,30 @@ describe Geo::ContainerRepositorySync, :geo do ...@@ -52,12 +57,30 @@ describe Geo::ContainerRepositorySync, :geo do
'Authorization' => 'bearer token' 'Authorization' => 'bearer token'
}) })
.to_return(status: 200, body: "", headers: { 'docker-content-digest' => 'sha256:aaaaa' }) .to_return(status: 200, body: "", headers: { 'docker-content-digest' => 'sha256:aaaaa' })
stub_request(:get, "http://primary.registry.gitlab/v2/group/test/my_image/manifests/tag-to-sync")
.with(
headers: {
'Accept' => 'application/vnd.docker.distribution.manifest.v2+json, application/vnd.oci.image.manifest.v1+json',
'Authorization' => 'bearer pull-token'
})
.to_return(status: 200, body: manifest, headers: {})
stub_request(:put, "http://registry.gitlab/v2/group/test/my_image/manifests/tag-to-sync")
.with(
body: manifest,
headers: {
'Accept' => 'application/vnd.docker.distribution.manifest.v2+json, application/vnd.oci.image.manifest.v1+json',
'Authorization' => 'bearer token',
'Content-Type' => 'application/json'
})
.to_return(status: 200, body: "", headers: {})
end end
describe 'execute' do describe 'execute' do
it 'determines list of tags to sync and to remove correctly' do it 'determines list of tags to sync and to remove correctly' do
expect(container_repository).to receive(:delete_tag_by_digest).with('sha256:aaaaa') expect(container_repository).to receive(:delete_tag_by_digest).with('sha256:aaaaa')
expect_any_instance_of(described_class).to receive(:sync_tag) expect_any_instance_of(described_class).to receive(:sync_tag).with('tag-to-sync').and_call_original
described_class.new(container_repository).execute described_class.new(container_repository).execute
end end
......
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