diff options
Diffstat (limited to 'app/services/projects/lfs_pointers/lfs_object_download_list_service.rb')
-rw-r--r-- | app/services/projects/lfs_pointers/lfs_object_download_list_service.rb | 96 |
1 files changed, 96 insertions, 0 deletions
diff --git a/app/services/projects/lfs_pointers/lfs_object_download_list_service.rb b/app/services/projects/lfs_pointers/lfs_object_download_list_service.rb new file mode 100644 index 00000000000..5ba0f50f2ff --- /dev/null +++ b/app/services/projects/lfs_pointers/lfs_object_download_list_service.rb @@ -0,0 +1,96 @@ +# frozen_string_literal: true + +# This service manages the whole worflow of discovering the Lfs files in a +# repository, linking them to the project and downloading (and linking) the non +# existent ones. +module Projects + module LfsPointers + class LfsObjectDownloadListService < BaseService + include Gitlab::Utils::StrongMemoize + + HEAD_REV = 'HEAD'.freeze + LFS_ENDPOINT_PATTERN = /^\t?url\s*=\s*(.+)$/.freeze + LFS_BATCH_API_ENDPOINT = '/info/lfs/objects/batch'.freeze + + LfsObjectDownloadListError = Class.new(StandardError) + + def execute + return [] unless project&.lfs_enabled? + + if external_lfs_endpoint? + # If the endpoint host is different from the import_url it means + # that the repo is using a third party service for storing the LFS files. + # In this case, we have to disable lfs in the project + disable_lfs! + + return [] + end + + # Getting all Lfs pointers already in the database and linking them to the project + linked_oids = LfsLinkService.new(project).execute(lfs_pointers_in_repository.keys) + # Retrieving those oids not present in the database which we need to download + missing_oids = lfs_pointers_in_repository.except(*linked_oids) # rubocop: disable CodeReuse/ActiveRecord + # Downloading the required information and gathering it inside a LfsDownloadObject for each oid + LfsDownloadLinkListService.new(project, remote_uri: current_endpoint_uri).execute(missing_oids) + rescue LfsDownloadLinkListService::DownloadLinksError => e + raise LfsObjectDownloadListError, "The LFS objects download list couldn't be imported. Error: #{e.message}" + end + + private + + def external_lfs_endpoint? + lfsconfig_endpoint_uri && lfsconfig_endpoint_uri.host != import_uri.host + end + + def disable_lfs! + unless project.update(lfs_enabled: false) + raise LfsDownloadLinkListService::DownloadLinksError, "Invalid project state" + end + end + + # Retrieves all lfs pointers in the repository + def lfs_pointers_in_repository + @lfs_pointers_in_repository ||= LfsListService.new(project).execute + end + + def lfsconfig_endpoint_uri + strong_memoize(:lfsconfig_endpoint_uri) do + # Retrieveing the blob data from the .lfsconfig file + data = project.repository.lfsconfig_for(HEAD_REV) + # Parsing the data to retrieve the url + parsed_data = data&.match(LFS_ENDPOINT_PATTERN) + + if parsed_data + URI.parse(parsed_data[1]).tap do |endpoint| + endpoint.user ||= import_uri.user + endpoint.password ||= import_uri.password + end + end + end + rescue URI::InvalidURIError + raise LfsObjectDownloadListError, 'Invalid URL in .lfsconfig file' + end + + def import_uri + @import_uri ||= URI.parse(project.import_url) + rescue URI::InvalidURIError + raise LfsObjectDownloadListError, 'Invalid project import URL' + end + + def current_endpoint_uri + (lfsconfig_endpoint_uri || default_endpoint_uri) + end + + # The import url must end with '.git' here we ensure it is + def default_endpoint_uri + @default_endpoint_uri ||= begin + import_uri.dup.tap do |uri| + path = uri.path.gsub(%r(/$), '') + path += '.git' unless path.ends_with?('.git') + uri.path = path + LFS_BATCH_API_ENDPOINT + end + end + end + end + end +end |