# frozen_string_literal: true require 'rubygems/package' module Packages module Rubygems class ProcessGemService include Gitlab::Utils::StrongMemoize include ExclusiveLeaseGuard ExtractionError = Class.new(StandardError) DEFAULT_LEASE_TIMEOUT = 1.hour.to_i.freeze def initialize(package_file) @package_file = package_file end def execute return success if process_gem error('Gem was not processed') end private attr_reader :package_file def process_gem return false unless package_file try_obtain_lease do package.transaction do rename_package_and_set_version rename_package_file ::Packages::Rubygems::MetadataExtractionService.new(package, gemspec).execute ::Packages::Rubygems::CreateGemspecService.new(package, gemspec).execute ::Packages::Rubygems::CreateDependenciesService.new(package, gemspec).execute cleanup_temp_package end end true end def rename_package_and_set_version package.update!( name: gemspec.name, version: gemspec.version, status: :default ) end def rename_package_file # Updating file_name updates the path where the file is stored. # We must pass the file again so that CarrierWave can handle the update package_file.update!( file_name: "#{gemspec.name}-#{gemspec.version}.gem", file: package_file.file, package_id: package.id ) end def cleanup_temp_package temp_package.destroy if package.id != temp_package.id end def gemspec strong_memoize(:gemspec) do gem.spec end end def success ServiceResponse.success(payload: { package: package }) end def error(message) ServiceResponse.error(message: message) end def temp_package strong_memoize(:temp_package) do package_file.package end end def package strong_memoize(:package) do # if package with name/version already exists, use that package package = temp_package.project .packages .rubygems .with_name(gemspec.name) .with_version(gemspec.version.to_s) .last package || temp_package end end def gem # use_file will set an exclusive lease on the file for as long as # the resulting gem object is being used. This means we are not # able to rename the package_file while also using the gem object. # We need to use a separate AR object to create the gem file to allow # `package_file` to be free for update so we re-find the file here. Packages::PackageFile.find(package_file.id).file.use_file do |file_path| Gem::Package.new(File.open(file_path)) end rescue raise ExtractionError.new('Unable to read gem file') end # used by ExclusiveLeaseGuard def lease_key "packages:rubygems:process_gem_service:package:#{package.id}" end # used by ExclusiveLeaseGuard def lease_timeout DEFAULT_LEASE_TIMEOUT end end end end