diff options
Diffstat (limited to 'app/services/packages/debian/generate_distribution_service.rb')
-rw-r--r-- | app/services/packages/debian/generate_distribution_service.rb | 207 |
1 files changed, 207 insertions, 0 deletions
diff --git a/app/services/packages/debian/generate_distribution_service.rb b/app/services/packages/debian/generate_distribution_service.rb new file mode 100644 index 00000000000..67348af1a49 --- /dev/null +++ b/app/services/packages/debian/generate_distribution_service.rb @@ -0,0 +1,207 @@ +# frozen_string_literal: true + +module Packages + module Debian + class GenerateDistributionService + include Gitlab::Utils::StrongMemoize + include ExclusiveLeaseGuard + + # used by ExclusiveLeaseGuard + DEFAULT_LEASE_TIMEOUT = 1.hour.to_i.freeze + + # From https://salsa.debian.org/ftp-team/dak/-/blob/991aaa27a7f7aa773bb9c0cf2d516e383d9cffa0/setup/core-init.d/080_metadatakeys#L9 + BINARIES_METADATA = %w( + Package + Source + Binary + Version + Essential + Installed-Size + Maintainer + Uploaders + Original-Maintainer + Build-Depends + Build-Depends-Indep + Build-Conflicts + Build-Conflicts-Indep + Architecture + Standards-Version + Format + Files + Dm-Upload-Allowed + Vcs-Browse + Vcs-Hg + Vcs-Darcs + Vcs-Svn + Vcs-Git + Vcs-Browser + Vcs-Arch + Vcs-Bzr + Vcs-Mtn + Vcs-Cvs + Checksums-Sha256 + Checksums-Sha1 + Replaces + Provides + Depends + Pre-Depends + Recommends + Suggests + Enhances + Conflicts + Breaks + Description + Origin + Bugs + Multi-Arch + Homepage + Tag + Package-Type + Installer-Menu-Item + ).freeze + + def initialize(distribution) + @distribution = distribution + @last_generated_at = nil + @md5sum = [] + @sha256 = [] + end + + def execute + try_obtain_lease do + @distribution.transaction do + @last_generated_at = @distribution.component_files.maximum(:created_at) + generate_component_files + generate_release + destroy_old_component_files + end + end + end + + private + + def generate_component_files + @distribution.components.ordered_by_name.each do |component| + @distribution.architectures.ordered_by_name.each do |architecture| + generate_component_file(component, :packages, architecture, :deb) + end + end + end + + def generate_component_file(component, component_file_type, architecture, package_file_type) + paragraphs = @distribution.package_files + .preload_debian_file_metadata + .with_debian_component_name(component.name) + .with_debian_architecture_name(architecture.name) + .with_debian_file_type(package_file_type) + .find_each + .map(&method(:package_stanza_from_fields)) + create_component_file(component, component_file_type, architecture, package_file_type, paragraphs.join("\n")) + end + + def package_stanza_from_fields(package_file) + [ + BINARIES_METADATA.map do |metadata_key| + rfc822_field(metadata_key, package_file.debian_fields[metadata_key]) + end, + rfc822_field('Section', package_file.debian_fields['Section'] || 'misc'), + rfc822_field('Priority', package_file.debian_fields['Priority'] || 'extra'), + rfc822_field('Filename', package_filename(package_file)), + rfc822_field('Size', package_file.size), + rfc822_field('MD5sum', package_file.file_md5), + rfc822_field('SHA256', package_file.file_sha256) + ].flatten.compact.join('') + end + + def package_filename(package_file) + letter = package_file.package.name.start_with?('lib') ? package_file.package.name[0..3] : package_file.package.name[0] + "#{pool_prefix(package_file)}/#{letter}/#{package_file.package.name}/#{package_file.file_name}" + end + + def pool_prefix(package_file) + case @distribution + when ::Packages::Debian::GroupDistribution + "pool/#{@distribution.codename}/#{package_file.package.project_id}" + else + "pool/#{@distribution.codename}/#{@distribution.container_id}" + end + end + + def create_component_file(component, component_file_type, architecture, package_file_type, content) + component_file = component.files.create!( + file_type: component_file_type, + architecture: architecture, + compression_type: nil, + file: CarrierWaveStringFile.new(content), + file_md5: Digest::MD5.hexdigest(content), + file_sha256: Digest::SHA256.hexdigest(content) + ) + @md5sum.append(" #{component_file.file_md5} #{component_file.size.to_s.rjust(8)} #{component_file.relative_path}") + @sha256.append(" #{component_file.file_sha256} #{component_file.size.to_s.rjust(8)} #{component_file.relative_path}") + end + + def generate_release + @distribution.file = CarrierWaveStringFile.new(release_header + release_sums) + @distribution.updated_at = release_date + @distribution.save! + end + + def release_header + strong_memoize(:release_header) do + [ + %w[origin label suite version codename].map do |attribute| + rfc822_field(attribute.capitalize, @distribution.attributes[attribute]) + end, + rfc822_field('Date', release_date.to_formatted_s(:rfc822)), + valid_until_field, + rfc822_field('NotAutomatic', !@distribution.automatic, !@distribution.automatic), + rfc822_field('ButAutomaticUpgrades', @distribution.automatic_upgrades, !@distribution.automatic && @distribution.automatic_upgrades), + rfc822_field('Architectures', @distribution.architectures.map { |architecture| architecture.name }.sort.join(' ')), + rfc822_field('Components', @distribution.components.map { |component| component.name }.sort.join(' ')), + rfc822_field('Description', @distribution.description) + ].flatten.compact.join('') + end + end + + def release_date + strong_memoize(:release_date) do + Time.now.utc + end + end + + def release_sums + ["MD5Sum:", @md5sum, "SHA256:", @sha256].flatten.compact.join("\n") + "\n" + end + + def rfc822_field(name, value, condition = true) + return unless condition + return if value.blank? + + "#{name}: #{value.to_s.gsub("\n\n", "\n.\n").gsub("\n", "\n ")}\n" + end + + def valid_until_field + return unless @distribution.valid_time_duration_seconds + + rfc822_field('Valid-Until', release_date.since(@distribution.valid_time_duration_seconds).to_formatted_s(:rfc822)) + end + + def destroy_old_component_files + # Only keep the last generation and one hour before + return if @last_generated_at.nil? + + @distribution.component_files.created_before(@last_generated_at - 1.hour).destroy_all # rubocop:disable Cop/DestroyAll + end + + # used by ExclusiveLeaseGuard + def lease_key + "packages:debian:generate_distribution_service:distribution:#{@distribution.id}" + end + + # used by ExclusiveLeaseGuard + def lease_timeout + DEFAULT_LEASE_TIMEOUT + end + end + end +end |