diff options
Diffstat (limited to 'spec/services/projects/container_repository/gitlab/cleanup_tags_service_spec.rb')
-rw-r--r-- | spec/services/projects/container_repository/gitlab/cleanup_tags_service_spec.rb | 183 |
1 files changed, 183 insertions, 0 deletions
diff --git a/spec/services/projects/container_repository/gitlab/cleanup_tags_service_spec.rb b/spec/services/projects/container_repository/gitlab/cleanup_tags_service_spec.rb new file mode 100644 index 00000000000..d2cdb667659 --- /dev/null +++ b/spec/services/projects/container_repository/gitlab/cleanup_tags_service_spec.rb @@ -0,0 +1,183 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Projects::ContainerRepository::Gitlab::CleanupTagsService do + using RSpec::Parameterized::TableSyntax + + include_context 'for a cleanup tags service' + + let_it_be(:user) { create(:user) } + let_it_be(:user) { create(:user) } + let_it_be(:project, reload: true) { create(:project, :private) } + + let(:repository) { create(:container_repository, :root, :import_done, project: project) } + let(:service) { described_class.new(container_repository: repository, current_user: user, params: params) } + let(:tags) { %w[latest A Ba Bb C D E] } + + before do + project.add_maintainer(user) if user + + stub_container_registry_config(enabled: true) + + stub_const("#{described_class}::TAGS_PAGE_SIZE", tags_page_size) + + one_hour_ago = 1.hour.ago + five_days_ago = 5.days.ago + six_days_ago = 6.days.ago + one_month_ago = 1.month.ago + + stub_tags( + { + 'latest' => one_hour_ago, + 'A' => one_hour_ago, + 'Ba' => five_days_ago, + 'Bb' => six_days_ago, + 'C' => one_month_ago, + 'D' => nil, + 'E' => nil + } + ) + end + + describe '#execute' do + subject { service.execute } + + context 'with several tags pages' do + let(:tags_page_size) { 2 } + + it_behaves_like 'handling invalid params' + + it_behaves_like 'when regex matching everything is specified', + delete_expectations: [%w[A], %w[Ba Bb], %w[C D], %w[E]] + + it_behaves_like 'when delete regex matching specific tags is used' + + it_behaves_like 'when delete regex matching specific tags is used with overriding allow regex' + + it_behaves_like 'with allow regex value', + delete_expectations: [%w[A], %w[C D], %w[E]] + + it_behaves_like 'when keeping only N tags', + delete_expectations: [%w[Bb]] + + it_behaves_like 'when not keeping N tags', + delete_expectations: [%w[A], %w[Ba Bb], %w[C]] + + context 'when removing keeping only 3' do + let(:params) do + { + 'name_regex_delete' => '.*', + 'keep_n' => 3 + } + end + + it_behaves_like 'not removing anything' + end + + it_behaves_like 'when removing older than 1 day', + delete_expectations: [%w[Ba Bb], %w[C]] + + it_behaves_like 'when combining all parameters', + delete_expectations: [%w[Bb], %w[C]] + + it_behaves_like 'when running a container_expiration_policy', + delete_expectations: [%w[Bb], %w[C]] + + context 'with a timeout' do + let(:params) do + { 'name_regex_delete' => '.*' } + end + + it 'removes the first few pages' do + expect(service).to receive(:timeout?).and_return(false, true) + + expect_delete(%w[A]) + expect_delete(%w[Ba Bb]) + + response = expected_service_response(status: :error, deleted: %w[A Ba Bb], original_size: 4) + + is_expected.to eq(response) + end + end + end + + context 'with a single tags page' do + let(:tags_page_size) { 1000 } + + it_behaves_like 'handling invalid params' + + it_behaves_like 'when regex matching everything is specified', + delete_expectations: [%w[A Ba Bb C D E]] + + it_behaves_like 'when delete regex matching specific tags is used' + + it_behaves_like 'when delete regex matching specific tags is used with overriding allow regex' + + it_behaves_like 'with allow regex value', + delete_expectations: [%w[A C D E]] + + it_behaves_like 'when keeping only N tags', + delete_expectations: [%w[Ba Bb C]] + + it_behaves_like 'when not keeping N tags', + delete_expectations: [%w[A Ba Bb C]] + + it_behaves_like 'when removing keeping only 3', + delete_expectations: [%w[Ba Bb C]] + + it_behaves_like 'when removing older than 1 day', + delete_expectations: [%w[Ba Bb C]] + + it_behaves_like 'when combining all parameters', + delete_expectations: [%w[Ba Bb C]] + + it_behaves_like 'when running a container_expiration_policy', + delete_expectations: [%w[Ba Bb C]] + end + end + + private + + def stub_tags(tags) + chunked = tags_page_size < tags.size + previous_last = nil + max_chunk_index = tags.size / tags_page_size + + tags.keys.in_groups_of(tags_page_size, false).each_with_index do |chunked_tag_names, index| + last = index == max_chunk_index + pagination_needed = chunked && !last + + response = { + pagination: pagination_needed ? pagination_with(last: chunked_tag_names.last) : {}, + response_body: chunked_tag_names.map do |name| + tag_raw_response(name, tags[name]) + end + } + + allow(repository.gitlab_api_client) + .to receive(:tags) + .with(repository.path, page_size: described_class::TAGS_PAGE_SIZE, last: previous_last) + .and_return(response) + previous_last = chunked_tag_names.last + end + end + + def pagination_with(last:) + { + next: { + uri: URI("http://test.org?last=#{last}") + } + } + end + + def tag_raw_response(name, timestamp) + timestamp_field = name.start_with?('B') ? 'updated_at' : 'created_at' + { + 'name' => name, + 'digest' => 'sha256:1234567890', + 'media_type' => 'application/vnd.oci.image.manifest.v1+json', + timestamp_field => timestamp&.iso8601 + } + end +end |