diff options
Diffstat (limited to 'spec/services/projects/container_repository')
3 files changed, 217 insertions, 221 deletions
diff --git a/spec/services/projects/container_repository/delete_tags_service_spec.rb b/spec/services/projects/container_repository/delete_tags_service_spec.rb index 3d065deefdf..3014ccbd7ba 100644 --- a/spec/services/projects/container_repository/delete_tags_service_spec.rb +++ b/spec/services/projects/container_repository/delete_tags_service_spec.rb @@ -3,21 +3,15 @@ require 'spec_helper' RSpec.describe Projects::ContainerRepository::DeleteTagsService do - let_it_be(:user) { create(:user) } - let_it_be(:project) { create(:project, :private) } - let_it_be(:repository) { create(:container_repository, :root, project: project) } + include_context 'container repository delete tags service shared context' - let(:params) { { tags: tags } } let(:service) { described_class.new(project, user, params) } - before do - stub_container_registry_config(enabled: true, - api_url: 'http://registry.gitlab', - host_port: 'registry.gitlab') - - stub_container_registry_tags( - repository: repository.path, - tags: %w(latest A Ba Bb C D E)) + let_it_be(:available_service_classes) do + [ + ::Projects::ContainerRepository::Gitlab::DeleteTagsService, + ::Projects::ContainerRepository::ThirdParty::DeleteTagsService + ] end RSpec.shared_examples 'logging a success response' do @@ -45,8 +39,54 @@ RSpec.describe Projects::ContainerRepository::DeleteTagsService do end end + RSpec.shared_examples 'calling the correct delete tags service' do |expected_service_class| + let(:service_response) { { status: :success, deleted: tags } } + let(:excluded_service_class) { available_service_classes.excluding(expected_service_class).first } + + before do + service_double = double + expect(expected_service_class).to receive(:new).with(repository, tags).and_return(service_double) + expect(excluded_service_class).not_to receive(:new) + expect(service_double).to receive(:execute).and_return(service_response) + end + + it { is_expected.to include(status: :success) } + + it_behaves_like 'logging a success response' + + context 'with an error service response' do + let(:service_response) { { status: :error, message: 'could not delete tags' } } + + it { is_expected.to include(status: :error) } + + it_behaves_like 'logging an error response' + end + end + + RSpec.shared_examples 'handling invalid params' do + context 'with invalid params' do + before do + expect(::Projects::ContainerRepository::Gitlab::DeleteTagsService).not_to receive(:new) + expect(::Projects::ContainerRepository::ThirdParty::DeleteTagsService).not_to receive(:new) + expect_any_instance_of(ContainerRegistry::Client).not_to receive(:delete_repository_tag_by_name) + end + + context 'when no params are specified' do + let_it_be(:params) { {} } + + it { is_expected.to include(status: :error) } + end + + context 'with empty tags' do + let_it_be(:tags) { [] } + + it { is_expected.to include(status: :error) } + end + end + end + describe '#execute' do - let(:tags) { %w[A] } + let(:tags) { %w[A Ba] } subject { service.execute(repository) } @@ -61,247 +101,58 @@ RSpec.describe Projects::ContainerRepository::DeleteTagsService do context 'when the registry supports fast delete' do context 'and the feature is enabled' do - let_it_be(:project) { create(:project, :private) } - let_it_be(:repository) { create(:container_repository, :root, project: project) } - before do allow(repository.client).to receive(:supports_tag_delete?).and_return(true) end - context 'with tags to delete' do - let_it_be(:tags) { %w[A Ba] } - - it 'deletes the tags by name' do - stub_delete_reference_request('A') - stub_delete_reference_request('Ba') - - expect_delete_tag_by_name('A') - expect_delete_tag_by_name('Ba') - - is_expected.to include(status: :success) - end - - it 'succeeds when tag delete returns 404' do - stub_delete_reference_request('A') - stub_delete_reference_request('Ba', 404) - - is_expected.to include(status: :success) - end - - it_behaves_like 'logging a success response' do - before do - stub_delete_reference_request('A') - stub_delete_reference_request('Ba') - end - end - - context 'with failures' do - context 'when the delete request fails' do - before do - stub_delete_reference_request('A', 500) - stub_delete_reference_request('Ba', 500) - end - - it { is_expected.to include(status: :error) } - - it_behaves_like 'logging an error response' - end - end - end - - context 'when no params are specified' do - let_it_be(:params) { {} } + it_behaves_like 'calling the correct delete tags service', ::Projects::ContainerRepository::Gitlab::DeleteTagsService - it 'does not remove anything' do - expect_any_instance_of(ContainerRegistry::Client).not_to receive(:delete_repository_tag_by_name) + it_behaves_like 'handling invalid params' - is_expected.to include(status: :error) + context 'with the real service' do + before do + stub_delete_reference_requests(tags) + expect_delete_tag_by_names(tags) end - end - context 'with empty tags' do - let_it_be(:tags) { [] } + it { is_expected.to include(status: :success) } - it 'does not remove anything' do - expect_any_instance_of(ContainerRegistry::Client).not_to receive(:delete_repository_tag_by_name) - - is_expected.to include(status: :error) - end + it_behaves_like 'logging a success response' end end context 'and the feature is disabled' do - let_it_be(:tags) { %w[A Ba] } - before do stub_feature_flags(container_registry_fast_tag_delete: false) - stub_upload("{\n \"config\": {\n }\n}", 'sha256:4435000728ee66e6a80e55637fc22725c256b61de344a2ecdeaac6bdb36e8bc3') - stub_put_manifest_request('A') - stub_put_manifest_request('Ba') end - it 'fallbacks to slow delete' do - expect(service).not_to receive(:fast_delete) - expect(service).to receive(:slow_delete).with(repository, tags).and_call_original - - expect_delete_tag_by_digest('sha256:dummy') + it_behaves_like 'calling the correct delete tags service', ::Projects::ContainerRepository::ThirdParty::DeleteTagsService - subject - end + it_behaves_like 'handling invalid params' - it_behaves_like 'logging a success response' do + context 'with the real service' do before do - allow(service).to receive(:slow_delete).and_call_original + stub_upload('sha256:4435000728ee66e6a80e55637fc22725c256b61de344a2ecdeaac6bdb36e8bc3') + tags.each { |tag| stub_put_manifest_request(tag) } expect_delete_tag_by_digest('sha256:dummy') end + + it { is_expected.to include(status: :success) } + + it_behaves_like 'logging a success response' end end end context 'when the registry does not support fast delete' do - let_it_be(:project) { create(:project, :private) } - let_it_be(:repository) { create(:container_repository, :root, project: project) } - before do - stub_tag_digest('latest', 'sha256:configA') - stub_tag_digest('A', 'sha256:configA') - stub_tag_digest('Ba', 'sha256:configB') - allow(repository.client).to receive(:supports_tag_delete?).and_return(false) end - context 'when no params are specified' do - let_it_be(:params) { {} } - - it 'does not remove anything' do - expect_any_instance_of(ContainerRegistry::Client).not_to receive(:delete_repository_tag_by_digest) + it_behaves_like 'calling the correct delete tags service', ::Projects::ContainerRepository::ThirdParty::DeleteTagsService - is_expected.to include(status: :error) - end - end - - context 'with empty tags' do - let_it_be(:tags) { [] } - - it 'does not remove anything' do - expect_any_instance_of(ContainerRegistry::Client).not_to receive(:delete_repository_tag_by_digest) - - is_expected.to include(status: :error) - end - end - - context 'with tags to delete' do - let_it_be(:tags) { %w[A Ba] } - - it 'deletes the tags using a dummy image' do - stub_upload("{\n \"config\": {\n }\n}", 'sha256:4435000728ee66e6a80e55637fc22725c256b61de344a2ecdeaac6bdb36e8bc3') - - stub_put_manifest_request('A') - stub_put_manifest_request('Ba') - - expect_delete_tag_by_digest('sha256:dummy') - - is_expected.to include(status: :success) - end - - it 'succeeds when tag delete returns 404' do - stub_upload("{\n \"config\": {\n }\n}", 'sha256:4435000728ee66e6a80e55637fc22725c256b61de344a2ecdeaac6bdb36e8bc3') - - stub_put_manifest_request('A') - stub_put_manifest_request('Ba') - - stub_request(:delete, "http://registry.gitlab/v2/#{repository.path}/manifests/sha256:dummy") - .to_return(status: 404, body: "", headers: {}) - - is_expected.to include(status: :success) - end - - it_behaves_like 'logging a success response' do - before do - stub_upload("{\n \"config\": {\n }\n}", 'sha256:4435000728ee66e6a80e55637fc22725c256b61de344a2ecdeaac6bdb36e8bc3') - stub_put_manifest_request('A') - stub_put_manifest_request('Ba') - expect_delete_tag_by_digest('sha256:dummy') - end - end - - context 'with failures' do - context 'when the dummy manifest generation fails' do - before do - stub_upload("{\n \"config\": {\n }\n}", 'sha256:4435000728ee66e6a80e55637fc22725c256b61de344a2ecdeaac6bdb36e8bc3', success: false) - end - - it { is_expected.to include(status: :error) } - - it_behaves_like 'logging an error response', message: 'could not generate manifest' - end - - context 'when updating the tags fails' do - before do - stub_upload("{\n \"config\": {\n }\n}", 'sha256:4435000728ee66e6a80e55637fc22725c256b61de344a2ecdeaac6bdb36e8bc3') - - stub_put_manifest_request('A', 500) - stub_put_manifest_request('Ba', 500) - - stub_request(:delete, "http://registry.gitlab/v2/#{repository.path}/manifests/sha256:4435000728ee66e6a80e55637fc22725c256b61de344a2ecdeaac6bdb36e8bc3") - .to_return(status: 200, body: "", headers: {}) - end - - it { is_expected.to include(status: :error) } - it_behaves_like 'logging an error response' - end - end - end + it_behaves_like 'handling invalid params' end end end - - private - - def stub_delete_reference_request(tag, status = 200) - stub_request(:delete, "http://registry.gitlab/v2/#{repository.path}/tags/reference/#{tag}") - .to_return(status: status, body: '') - end - - def stub_put_manifest_request(tag, status = 200, headers = { 'docker-content-digest' => 'sha256:dummy' }) - stub_request(:put, "http://registry.gitlab/v2/#{repository.path}/manifests/#{tag}") - .to_return(status: status, body: '', headers: headers) - end - - def stub_tag_digest(tag, digest) - stub_request(:head, "http://registry.gitlab/v2/#{repository.path}/manifests/#{tag}") - .to_return(status: 200, body: "", headers: { 'docker-content-digest' => digest }) - end - - def stub_digest_config(digest, created_at) - allow_any_instance_of(ContainerRegistry::Client) - .to receive(:blob) - .with(repository.path, digest, nil) do - { 'created' => created_at.to_datetime.rfc3339 }.to_json if created_at - end - end - - def stub_upload(content, digest, success: true) - expect_any_instance_of(ContainerRegistry::Client) - .to receive(:upload_blob) - .with(repository.path, content, digest) { double(success?: success ) } - end - - def expect_delete_tag_by_digest(digest) - expect_any_instance_of(ContainerRegistry::Client) - .to receive(:delete_repository_tag_by_digest) - .with(repository.path, digest) { true } - - expect_any_instance_of(ContainerRegistry::Client) - .not_to receive(:delete_repository_tag_by_name) - end - - def expect_delete_tag_by_name(name) - expect_any_instance_of(ContainerRegistry::Client) - .to receive(:delete_repository_tag_by_name) - .with(repository.path, name) { true } - - expect_any_instance_of(ContainerRegistry::Client) - .not_to receive(:delete_repository_tag_by_digest) - end end diff --git a/spec/services/projects/container_repository/gitlab/delete_tags_service_spec.rb b/spec/services/projects/container_repository/gitlab/delete_tags_service_spec.rb new file mode 100644 index 00000000000..68c232e5d83 --- /dev/null +++ b/spec/services/projects/container_repository/gitlab/delete_tags_service_spec.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Projects::ContainerRepository::Gitlab::DeleteTagsService do + include_context 'container repository delete tags service shared context' + + let(:service) { described_class.new(repository, tags) } + + describe '#execute' do + let(:tags) { %w[A Ba] } + + subject { service.execute } + + context 'with tags to delete' do + it 'deletes the tags by name' do + stub_delete_reference_requests(tags) + expect_delete_tag_by_names(tags) + + is_expected.to eq(status: :success, deleted: tags) + end + + it 'succeeds when tag delete returns 404' do + stub_delete_reference_requests('A' => 200, 'Ba' => 404) + + is_expected.to eq(status: :success, deleted: tags) + end + + it 'succeeds when a tag delete returns 500' do + stub_delete_reference_requests('A' => 200, 'Ba' => 500) + + is_expected.to eq(status: :success, deleted: ['A']) + end + + context 'with failures' do + context 'when the delete request fails' do + before do + stub_delete_reference_requests('A' => 500, 'Ba' => 500) + end + + it { is_expected.to eq(status: :error, message: 'could not delete tags') } + end + end + end + + context 'with empty tags' do + let_it_be(:tags) { [] } + + it 'does not remove anything' do + expect_any_instance_of(ContainerRegistry::Client).not_to receive(:delete_repository_tag_by_name) + + is_expected.to eq(status: :success, deleted: []) + end + end + end +end diff --git a/spec/services/projects/container_repository/third_party/delete_tags_service_spec.rb b/spec/services/projects/container_repository/third_party/delete_tags_service_spec.rb new file mode 100644 index 00000000000..7fc963949eb --- /dev/null +++ b/spec/services/projects/container_repository/third_party/delete_tags_service_spec.rb @@ -0,0 +1,89 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Projects::ContainerRepository::ThirdParty::DeleteTagsService do + include_context 'container repository delete tags service shared context' + + let(:service) { described_class.new(repository, tags) } + + describe '#execute' do + let(:tags) { %w[A Ba] } + + subject { service.execute } + + context 'with tags to delete' do + it 'deletes the tags by name' do + stub_upload('sha256:4435000728ee66e6a80e55637fc22725c256b61de344a2ecdeaac6bdb36e8bc3') + + tags.each { |tag| stub_put_manifest_request(tag) } + + expect_delete_tag_by_digest('sha256:dummy') + + is_expected.to eq(status: :success, deleted: tags) + end + + it 'succeeds when tag delete returns 404' do + stub_upload('sha256:4435000728ee66e6a80e55637fc22725c256b61de344a2ecdeaac6bdb36e8bc3') + + stub_put_manifest_request('A') + stub_put_manifest_request('Ba') + + stub_request(:delete, "http://registry.gitlab/v2/#{repository.path}/manifests/sha256:dummy") + .to_return(status: 404, body: '', headers: {}) + + is_expected.to eq(status: :success, deleted: tags) + end + + context 'with failures' do + context 'when the dummy manifest generation fails' do + before do + stub_upload('sha256:4435000728ee66e6a80e55637fc22725c256b61de344a2ecdeaac6bdb36e8bc3', success: false) + end + + it { is_expected.to eq(status: :error, message: 'could not generate manifest') } + end + + context 'when updating tags fails' do + before do + stub_upload('sha256:4435000728ee66e6a80e55637fc22725c256b61de344a2ecdeaac6bdb36e8bc3') + + stub_request(:delete, "http://registry.gitlab/v2/#{repository.path}/manifests/sha256:4435000728ee66e6a80e55637fc22725c256b61de344a2ecdeaac6bdb36e8bc3") + .to_return(status: 200, body: '', headers: {}) + end + + context 'all tag updates fail' do + before do + stub_put_manifest_request('A', 500, {}) + stub_put_manifest_request('Ba', 500, {}) + end + + it { is_expected.to eq(status: :error, message: 'could not delete tags') } + end + + context 'a single tag update fails' do + before do + stub_put_manifest_request('A') + stub_put_manifest_request('Ba', 500, {}) + + stub_request(:delete, "http://registry.gitlab/v2/#{repository.path}/manifests/sha256:dummy") + .to_return(status: 404, body: '', headers: {}) + end + + it { is_expected.to eq(status: :success, deleted: ['A']) } + end + end + end + end + + context 'with empty tags' do + let_it_be(:tags) { [] } + + it 'does not remove anything' do + expect_any_instance_of(ContainerRegistry::Client).not_to receive(:delete_repository_tag_by_name) + + is_expected.to eq(status: :success, deleted: []) + end + end + end +end |