Commit a8022831 authored by Michael Kozono's avatar Michael Kozono

Merge branch...

Merge branch '329231-geo-container-registry-sync-does-not-sync-images-with-foreign-layers' into 'master'

Geo - Skip download of blobs with foreign layers

See merge request gitlab-org/gitlab!61072
parents 0138e330 fe9c323f
...@@ -6,21 +6,18 @@ module Geo ...@@ -6,21 +6,18 @@ module Geo
class ContainerRepositorySync class ContainerRepositorySync
include Gitlab::Utils::StrongMemoize include Gitlab::Utils::StrongMemoize
attr_reader :name, :container_repository FOREIGN_MEDIA_TYPE = 'application/vnd.docker.image.rootfs.foreign.diff.tar.gzip'
attr_reader :repository_path, :container_repository
def initialize(container_repository) def initialize(container_repository)
@container_repository = container_repository @container_repository = container_repository
@name = container_repository.path @repository_path = container_repository.path
end end
def execute def execute
tags_to_sync.each do |tag| tags_to_sync.each { |tag| sync_tag(tag) }
sync_tag(tag[:name]) tags_to_remove.each { |tag| remove_tag(tag) }
end
tags_to_remove.each do |tag|
container_repository.delete_tag_by_digest(tag[:digest])
end
true true
end end
...@@ -29,44 +26,51 @@ module Geo ...@@ -29,44 +26,51 @@ module Geo
def sync_tag(tag) def sync_tag(tag)
file = nil file = nil
manifest = client.repository_raw_manifest(name, tag) manifest = client.repository_raw_manifest(repository_path, tag[:name])
manifest_parsed = Gitlab::Json.parse(manifest) manifest_parsed = Gitlab::Json.parse(manifest)
list_blobs(manifest_parsed).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(repository_path, digest)
container_repository.push_blob(digest, file.path) container_repository.push_blob(digest, file.path)
file.unlink file.unlink
end end
container_repository.push_manifest(tag, manifest, manifest_parsed['mediaType']) container_repository.push_manifest(tag[:name], manifest, manifest_parsed['mediaType'])
ensure ensure
file.try(:unlink) file.try(:unlink)
end end
def remove_tag(tag)
container_repository.delete_tag_by_digest(tag[:digest])
end
def list_blobs(manifest) def list_blobs(manifest)
layers = manifest['layers'].map do |layer| layers = manifest['layers'].filter_map do |layer|
layer['digest'] layer['digest'] unless foreign_layer?(layer)
end end
layers.push(manifest.dig('config', 'digest')).compact layers.push(manifest.dig('config', 'digest')).compact
end end
def primary_tags def foreign_layer?(layer)
@primary_tags ||= begin layer['mediaType'] == FOREIGN_MEDIA_TYPE
manifest = client.repository_tags(name) end
return [] unless manifest && manifest['tags'] def primary_tags
strong_memoize(:primary_tags) do
manifest = client.repository_tags(repository_path)
next [] unless manifest && manifest['tags']
manifest['tags'].map do |tag| manifest['tags'].map do |tag|
{ name: tag, digest: client.repository_tag_digest(name, tag) } { name: tag, digest: client.repository_tag_digest(repository_path, tag) }
end end
end end
end end
def secondary_tags def secondary_tags
@secondary_tags ||= begin strong_memoize(:secondary_tags) do
container_repository.tags.map do |tag| container_repository.tags.map do |tag|
{ name: tag.name, digest: tag.digest } { name: tag.name, digest: tag.digest }
end end
...@@ -86,7 +90,7 @@ module Geo ...@@ -86,7 +90,7 @@ module Geo
strong_memoize(:client) do strong_memoize(:client) do
ContainerRegistry::Client.new( ContainerRegistry::Client.new(
Gitlab.config.geo.registry_replication.primary_api_url, Gitlab.config.geo.registry_replication.primary_api_url,
token: ::Auth::ContainerRegistryAuthenticationService.pull_access_token(name) token: ::Auth::ContainerRegistryAuthenticationService.pull_access_token(repository_path)
) )
end end
end end
......
---
title: Geo - Skip download of blobs with foreign layers
merge_request: 61072
author:
type: fixed
...@@ -3,102 +3,108 @@ ...@@ -3,102 +3,108 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe Geo::ContainerRepositorySync, :geo do RSpec.describe Geo::ContainerRepositorySync, :geo do
let(:group) { create(:group, name: 'group') } let_it_be(:group) { create(:group, name: 'group') }
let(:project) { create(:project, path: 'test', group: group) } let_it_be(:project) { create(:project, path: 'test', group: group) }
let_it_be(:container_repository) { create(:container_repository, name: 'my_image', project: project) }
let(:container_repository) do
create(:container_repository, name: 'my_image', project: project) let(:primary_api_url) { 'http://primary.registry.gitlab' }
let(:secondary_api_url) { 'http://registry.gitlab' }
let(:primary_repository_url) { "#{primary_api_url}/v2/#{container_repository.path}" }
let(:secondary_repository_url ) { "#{secondary_api_url}/v2/#{container_repository.path}" }
# 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) do
"{" \
"\n\"schemaVersion\":2," \
"\n\"layers\":[" \
"{\n\"mediaType\":\"application/vnd.docker.distribution.manifest.v2+json\",\n\"size\":3333,\n\"digest\":\"sha256:3333\"}," \
"{\n\"mediaType\":\"application/vnd.docker.distribution.manifest.v2+json\",\n\"size\":4444,\n\"digest\":\"sha256:4444\"}," \
"{\n\"mediaType\":\"application/vnd.docker.image.rootfs.foreign.diff.tar.gzip\",\n\"size\":5555,\n\"digest\":\"sha256:5555\",\n\"urls\":[\"https://foo.bar/v2/zoo/blobs/sha256:5555\"]}" \
"]" \
"}"
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: secondary_api_url)
api_url: 'http://registry.gitlab', stub_registry_replication_config(enabled: true, primary_api_url: primary_api_url)
host_port: 'registry.gitlab') end
stub_registry_replication_config(enabled: true, def stub_primary_repository_tags_requests(repository_url, tags)
primary_api_url: 'http://primary.registry.gitlab') stub_request(:get, "#{repository_url}/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, application/vnd.oci.image.manifest.v1+json',
'Authorization' => 'bearer token'
})
.to_return( .to_return(
status: 200, status: 200,
body: Gitlab::Json.dump(tags: %w(obsolete)), body: Gitlab::Json.dump(tags: tags.keys),
headers: { 'Content-Type' => 'application/json' }) headers: { 'Content-Type' => 'application/json' })
stub_request(:get, "http://primary.registry.gitlab/v2/group/test/my_image/tags/list") tags.each do |tag, digest|
.with( stub_request(:head, "#{repository_url}/manifests/#{tag}")
headers: { 'Authorization' => 'bearer pull-token' }) .to_return(status: 200, body: "", headers: { 'docker-content-digest' => digest })
end
end
def stub_secondary_repository_tags_requests(repository_url, tags)
stub_request(:get, "#{repository_url}/tags/list")
.to_return( .to_return(
status: 200, status: 200,
body: Gitlab::Json.dump(tags: %w(tag-to-sync)), body: Gitlab::Json.dump(tags: tags.keys),
headers: { 'Content-Type' => 'application/json' }) headers: { 'Content-Type' => 'application/json' })
stub_request(:head, "http://primary.registry.gitlab/v2/group/test/my_image/manifests/tag-to-sync") tags.each do |tag, digest|
.with( stub_request(:head, "#{repository_url}/manifests/#{tag}")
headers: { .to_return(status: 200, body: "", headers: { 'docker-content-digest' => digest })
'Accept' => 'application/vnd.docker.distribution.manifest.v2+json, application/vnd.oci.image.manifest.v1+json', end
'Authorization' => 'bearer pull-token' end
})
.to_return(status: 200, body: "", headers: { 'docker-content-digest' => 'sha256:ccccc' }) def stub_primary_raw_manifest_request(repository_url, tag, manifest)
stub_request(:get, "#{repository_url}/manifests/#{tag}")
stub_request(:head, "http://registry.gitlab/v2/group/test/my_image/manifests/obsolete")
.with(
headers: {
'Accept' => 'application/vnd.docker.distribution.manifest.v2+json, application/vnd.oci.image.manifest.v1+json',
'Authorization' => 'bearer token'
})
.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: {}) .to_return(status: 200, body: manifest, headers: {})
end
stub_request(:put, "http://registry.gitlab/v2/group/test/my_image/manifests/tag-to-sync") def stub_secondary_push_manifest_request(repository_url, tag, manifest)
.with( stub_request(:put, "#{repository_url}/manifests/#{tag}")
body: manifest, .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: {}) .to_return(status: 200, body: "", headers: {})
end end
describe 'execute' do def stub_missing_blobs_requests(primary_repository_url, secondary_repository_url, blobs)
it 'determines list of tags to sync and to remove correctly' do blobs.each do |digest, missing|
expect(container_repository).to receive(:delete_tag_by_digest).with('sha256:aaaaa') stub_request(:head, "#{secondary_repository_url}/blobs/#{digest}")
expect_next_instance_of(described_class) do |instance| .to_return(status: (missing ? 404 : 200), body: "", headers: {})
expect(instance).to receive(:sync_tag).with('tag-to-sync').and_call_original
end
described_class.new(container_repository).execute next unless missing
stub_request(:get, "#{primary_repository_url}/blobs/#{digest}")
.to_return(status: 200, body: File.new(Rails.root.join('ee/spec/fixtures/ee_sample_schema.json')), headers: {})
end
end
describe '#execute' do
subject { described_class.new(container_repository) }
it 'determines list of tags to sync and to remove correctly' do
stub_primary_repository_tags_requests(primary_repository_url, { 'tag-to-sync' => 'sha256:1111' })
stub_secondary_repository_tags_requests(secondary_repository_url, { 'tag-to-remove' => 'sha256:2222' })
stub_primary_raw_manifest_request(primary_repository_url, 'tag-to-sync', manifest)
stub_missing_blobs_requests(primary_repository_url, secondary_repository_url, { 'sha256:3333' => true, 'sha256:4444' => false })
stub_secondary_push_manifest_request(secondary_repository_url, 'tag-to-sync', manifest)
expect(container_repository).to receive(:push_blob).with('sha256:3333', anything)
expect(container_repository).not_to receive(:push_blob).with('sha256:4444', anything)
expect(container_repository).not_to receive(:push_blob).with('sha256:5555', anything)
expect(container_repository).to receive(:delete_tag_by_digest).with('sha256:2222')
subject.execute
end end
context 'when primary repository has no tags' do context 'when primary repository has no tags' do
it 'considers the primary repository empty and does not fail' do it 'removes secondary tags and does not fail' do
stub_request(:get, "http://primary.registry.gitlab/v2/group/test/my_image/tags/list") stub_primary_repository_tags_requests(primary_repository_url, {})
.with( stub_secondary_repository_tags_requests(secondary_repository_url, { 'tag-to-remove' => 'sha256:2222' })
headers: { 'Authorization' => 'bearer pull-token' })
.to_return(
status: 200,
headers: { 'Content-Type' => 'application/json' })
expect(container_repository).to receive(:delete_tag_by_digest).with('sha256:aaaaa') expect(container_repository).to receive(:delete_tag_by_digest).with('sha256:2222')
described_class.new(container_repository).execute subject.execute
end end
end end
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