diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml index 89d25502c6b42edf240aa85cd62ef557d6c16b25..bba5ef370519a0012e9049aaa5c4b77bf2f73f2f 100644 --- a/app/workers/all_queues.yml +++ b/app/workers/all_queues.yml @@ -1659,6 +1659,15 @@ :weight: 1 :idempotent: true :tags: [] +- :name: package_repositories:packages_debian_process_package_file + :worker_name: Packages::Debian::ProcessPackageFileWorker + :feature_category: :package_registry + :has_external_dependencies: false + :urgency: :low + :resource_boundary: :unknown + :weight: 1 + :idempotent: true + :tags: [] - :name: package_repositories:packages_go_sync_packages :worker_name: Packages::Go::SyncPackagesWorker :feature_category: :package_registry diff --git a/app/workers/packages/debian/process_package_file_worker.rb b/app/workers/packages/debian/process_package_file_worker.rb new file mode 100644 index 0000000000000000000000000000000000000000..587c0b78c9ce2fe6877ec14be990d8c0b913f0d9 --- /dev/null +++ b/app/workers/packages/debian/process_package_file_worker.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +module Packages + module Debian + class ProcessPackageFileWorker + include ApplicationWorker + include ::Packages::FIPS + include Gitlab::Utils::StrongMemoize + + data_consistency :always + + deduplicate :until_executed + idempotent! + + queue_namespace :package_repositories + feature_category :package_registry + + def perform(package_file_id, user_id, distribution_name, component_name) + raise DisabledError, 'Debian registry is not FIPS compliant' if Gitlab::FIPS.enabled? + + @package_file_id = package_file_id + @user_id = user_id + @distribution_name = distribution_name + @component_name = component_name + + return unless package_file && user && distribution_name && component_name + # return if file has already been processed + return unless package_file.debian_file_metadatum&.unknown? + + ::Packages::Debian::ProcessPackageFileService.new(package_file, user, distribution_name, component_name).execute + rescue StandardError => e + raise if e.instance_of?(DisabledError) + + Gitlab::ErrorTracking.log_exception(e, package_file_id: @package_file_id, user_id: @user_id, + distribution_name: @distribution_name, component_name: @component_name) + package_file.destroy! + end + + private + + def package_file + ::Packages::PackageFile.find_by_id(@package_file_id) + end + strong_memoize_attr :package_file + + def user + ::User.find_by_id(@user_id) + end + strong_memoize_attr :user + end + end +end diff --git a/spec/services/packages/debian/process_package_file_service_spec.rb b/spec/services/packages/debian/process_package_file_service_spec.rb index f3d6cdee7b4be96618ad14bc4ee34413b1f389da..571861f42cf40690378b2e4835b0c3d4d9acc1be 100644 --- a/spec/services/packages/debian/process_package_file_service_spec.rb +++ b/spec/services/packages/debian/process_package_file_service_spec.rb @@ -109,7 +109,8 @@ end context 'with already processed package file' do - let!(:package_file) { create(:debian_package_file) } + let_it_be(:package_file) { create(:debian_package_file) } + let(:component_name) { 'main' } it 'raise ArgumentError', :aggregate_failures do diff --git a/spec/workers/packages/debian/process_package_file_worker_spec.rb b/spec/workers/packages/debian/process_package_file_worker_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..532bfb096a38d7f64d120d0c8cad03754c7874b4 --- /dev/null +++ b/spec/workers/packages/debian/process_package_file_worker_spec.rb @@ -0,0 +1,138 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Packages::Debian::ProcessPackageFileWorker, type: :worker, feature_category: :package_registry do + let_it_be(:user) { create(:user) } + let_it_be_with_reload(:distribution) { create(:debian_project_distribution, :with_file, codename: 'unstable') } + + let(:incoming) { create(:debian_incoming, project: distribution.project) } + let(:distribution_name) { distribution.codename } + let(:worker) { described_class.new } + + describe '#perform' do + let(:package_file_id) { package_file.id } + let(:user_id) { user.id } + + subject { worker.perform(package_file_id, user_id, distribution_name, component_name) } + + shared_examples 'returns early without error' do + it 'returns early without error' do + expect(Gitlab::ErrorTracking).not_to receive(:log_exception) + expect(::Packages::Debian::ProcessPackageFileService).not_to receive(:new) + + subject + end + end + + using RSpec::Parameterized::TableSyntax + + where(:case_name, :expected_file_type, :file_name, :component_name) do + 'with a deb' | 'deb' | 'libsample0_1.2.3~alpha2_amd64.deb' | 'main' + 'with an udeb' | 'udeb' | 'sample-udeb_1.2.3~alpha2_amd64.udeb' | 'contrib' + end + + with_them do + context 'with Debian package file' do + let(:package_file) { incoming.package_files.with_file_name(file_name).first } + + context 'with mocked service' do + it 'calls ProcessPackageFileService' do + expect(Gitlab::ErrorTracking).not_to receive(:log_exception) + expect_next_instance_of(::Packages::Debian::ProcessPackageFileService) do |service| + expect(service).to receive(:execute) + .with(no_args) + end + + subject + end + end + + context 'with non existing user' do + let(:user_id) { non_existing_record_id } + + it_behaves_like 'returns early without error' + end + + context 'with nil user id' do + let(:user_id) { nil } + + it_behaves_like 'returns early without error' + end + + context 'when the service raises an error' do + let(:package_file) { incoming.package_files.with_file_name('sample_1.2.3~alpha2.tar.xz').first } + + it 'removes package file', :aggregate_failures do + expect(Gitlab::ErrorTracking).to receive(:log_exception).with( + instance_of(ArgumentError), + package_file_id: package_file_id, + user_id: user_id, + distribution_name: distribution_name, + component_name: component_name + ) + expect { subject } + .to not_change(Packages::Package, :count) + .and change { Packages::PackageFile.count }.by(-1) + .and change { incoming.package_files.count }.from(7).to(6) + + expect { package_file.reload }.to raise_error(ActiveRecord::RecordNotFound) + end + end + + it_behaves_like 'an idempotent worker' do + let(:job_args) { [package_file.id, user.id, distribution_name, component_name] } + + it 'sets the Debian file type as deb', :aggregate_failures do + expect(Gitlab::ErrorTracking).not_to receive(:log_exception) + + # Using subject inside this block will process the job multiple times + expect { subject } + .to change { Packages::Package.count }.from(1).to(2) + .and not_change(Packages::PackageFile, :count) + .and change { incoming.package_files.count }.from(7).to(6) + .and change { + package_file&.debian_file_metadatum&.reload&.file_type + }.from('unknown').to(expected_file_type) + + created_package = Packages::Package.last + expect(created_package.name).to eq 'sample' + expect(created_package.version).to eq '1.2.3~alpha2' + expect(created_package.creator).to eq user + end + end + end + end + + context 'with already processed package file' do + let_it_be(:package_file) { create(:debian_package_file) } + + let(:component_name) { 'main' } + + it_behaves_like 'returns early without error' + end + + context 'with a deb' do + let(:package_file) { incoming.package_files.with_file_name('libsample0_1.2.3~alpha2_amd64.deb').first } + let(:component_name) { 'main' } + + context 'with FIPS mode enabled', :fips_mode do + it 'raises an error' do + expect { subject }.to raise_error(::Packages::FIPS::DisabledError) + end + end + + context 'with non existing package file' do + let(:package_file_id) { non_existing_record_id } + + it_behaves_like 'returns early without error' + end + + context 'with nil package file id' do + let(:package_file_id) { nil } + + it_behaves_like 'returns early without error' + end + end + end +end