diff options
12 files changed, 232 insertions, 25 deletions
diff --git a/doc/user/project/packages/maven_repository.md b/doc/user/project/packages/maven_repository.md index a84761006ea..0e369131c64 100644 --- a/doc/user/project/packages/maven_repository.md +++ b/doc/user/project/packages/maven_repository.md @@ -119,6 +119,31 @@ on the home page of your project. If you have a self-hosted GitLab installation, replace `gitlab.com` with your domain name. +## Instance level Maven endpoint + +> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/8274) in GitLab Premium 11.6. + +If you rely on many packages, it might be inefficient to include the `repository` section +with a unique URL for each package. Instead, you can use the instance level endpoint for +all maven packages stored in GitLab. Only packages you have access to +will be available for download. Here's how the relevant `repository` section of +your `pom.xml` would look like: + +```xml +<repositories> + <repository> + <id>gitlab-maven</id> + <url>https://gitlab.com/api/v4/packages/maven</url> + </repository> +</repositories> +``` + +If you have a self-hosted GitLab installation, replace `gitlab.com` with your +domain name. + +You still need a project specific URL for uploading a package +in the `distributionManagement` section. + ## Uploading packages Once you have set up the [authorization](#authorizing-with-the-gitlab-maven-repository) diff --git a/ee/app/finders/packages/maven_package_finder.rb b/ee/app/finders/packages/maven_package_finder.rb index 7c970a262b8..6297b5325a4 100644 --- a/ee/app/finders/packages/maven_package_finder.rb +++ b/ee/app/finders/packages/maven_package_finder.rb @@ -1,10 +1,10 @@ # frozen_string_literal: true class Packages::MavenPackageFinder - attr_reader :project, :path + attr_reader :path, :project - def initialize(project, path) - @project = project + def initialize(path, project = nil) @path = path + @project = project end def execute @@ -17,9 +17,17 @@ class Packages::MavenPackageFinder private + def scope + if project + project.packages + else + ::Packages::Package.all + end + end + # rubocop: disable CodeReuse/ActiveRecord def packages - project.packages.joins(:maven_metadatum) + scope.joins(:maven_metadatum) .where(packages_maven_metadata: { path: path }) end # rubocop: enable CodeReuse/ActiveRecord diff --git a/ee/app/services/geo/repository_destroy_service.rb b/ee/app/services/geo/repository_destroy_service.rb index 89e354544ed..b4059a40632 100644 --- a/ee/app/services/geo/repository_destroy_service.rb +++ b/ee/app/services/geo/repository_destroy_service.rb @@ -1,5 +1,7 @@ module Geo class RepositoryDestroyService + include ::Gitlab::Geo::LogHelpers + attr_reader :id, :name, :disk_path, :repository_storage def initialize(id, name, disk_path, repository_storage) @@ -14,11 +16,24 @@ module Geo end def execute - ::Projects::DestroyService.new(deleted_project, nil).geo_replicate + destroy_project + delete_project_registry_entries end private + def destroy_project + ::Projects::DestroyService.new(deleted_project, nil).geo_replicate + end + + # rubocop: disable CodeReuse/ActiveRecord + def delete_project_registry_entries + ::Geo::ProjectRegistry.where(project_id: id).delete_all + + log_info("Project registry entry removed", project_id: id) + end + # rubocop: enable CodeReuse/ActiveRecord + def deleted_project # We don't have access to the original model anymore, so we are # rebuilding only what our service class requires diff --git a/ee/app/services/packages/find_or_create_maven_package_service.rb b/ee/app/services/packages/find_or_create_maven_package_service.rb index 141f31a2169..1e43bf863b5 100644 --- a/ee/app/services/packages/find_or_create_maven_package_service.rb +++ b/ee/app/services/packages/find_or_create_maven_package_service.rb @@ -5,7 +5,7 @@ module Packages def execute package = ::Packages::MavenPackageFinder - .new(project, params[:path]).execute + .new(params[:path], project).execute unless package if params[:file_name] == MAVEN_METADATA_FILE diff --git a/ee/changelogs/unreleased/7769-instance-level-maven-endpoint.yml b/ee/changelogs/unreleased/7769-instance-level-maven-endpoint.yml new file mode 100644 index 00000000000..8c7a8b74be7 --- /dev/null +++ b/ee/changelogs/unreleased/7769-instance-level-maven-endpoint.yml @@ -0,0 +1,5 @@ +--- +title: Add an instance-level endpoint for downloading maven packages +merge_request: 8274 +author: +type: added diff --git a/ee/changelogs/unreleased/7960-geo-address-the-update-delete-race.yml b/ee/changelogs/unreleased/7960-geo-address-the-update-delete-race.yml new file mode 100644 index 00000000000..f5e58825bc2 --- /dev/null +++ b/ee/changelogs/unreleased/7960-geo-address-the-update-delete-race.yml @@ -0,0 +1,6 @@ +--- +title: 'Geo: Moving registry deletion into the job that deletes the files and project + record' +merge_request: 8480 +author: +type: fixed diff --git a/ee/lib/api/maven_packages.rb b/ee/lib/api/maven_packages.rb index c386e6d7243..070b484f21a 100644 --- a/ee/lib/api/maven_packages.rb +++ b/ee/lib/api/maven_packages.rb @@ -12,7 +12,6 @@ module API before do require_packages_enabled! authenticate_non_get! - authorize_packages_feature! end helpers do @@ -54,10 +53,44 @@ module API end end + desc 'Download the maven package file at instance level' do + detail 'This feature was introduced in GitLab 11.6' + end + params do + requires :path, type: String, desc: 'Package path' + requires :file_name, type: String, desc: 'Package file name' + end + route_setting :authentication, job_token_allowed: true + get 'packages/maven/*path/:file_name', requirements: MAVEN_ENDPOINT_REQUIREMENTS do + file_name, format = extract_format(params[:file_name]) + + package = ::Packages::MavenPackageFinder.new(params[:path]).execute! + + forbidden! unless package.project.feature_available?(:packages) + + authorize!(:read_package, package.project) + + package_file = ::Packages::PackageFileFinder + .new(package, file_name).execute! + + case format + when 'md5' + package_file.file_md5 + when 'sha1' + package_file.file_sha1 + when nil + present_carrierwave_file!(package_file.file) + end + end + params do requires :id, type: String, desc: 'The ID of a project' end resource :projects, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do + before do + authorize_packages_feature! + end + desc 'Download the maven package file' do detail 'This feature was introduced in GitLab 11.3' end @@ -72,7 +105,7 @@ module API file_name, format = extract_format(params[:file_name]) package = ::Packages::MavenPackageFinder - .new(user_project, params[:path]).execute! + .new(params[:path], user_project).execute! package_file = ::Packages::PackageFileFinder .new(package, file_name).execute! diff --git a/ee/lib/gitlab/geo/log_cursor/events/repository_deleted_event.rb b/ee/lib/gitlab/geo/log_cursor/events/repository_deleted_event.rb index 3560a5c9338..14b014dff83 100644 --- a/ee/lib/gitlab/geo/log_cursor/events/repository_deleted_event.rb +++ b/ee/lib/gitlab/geo/log_cursor/events/repository_deleted_event.rb @@ -10,7 +10,6 @@ module Gitlab unless skippable? job_id = destroy_repository - delete_project_registry_entries end log_event(job_id) @@ -28,12 +27,6 @@ module Gitlab ).async_execute end - # rubocop: disable CodeReuse/ActiveRecord - def delete_project_registry_entries - ::Geo::ProjectRegistry.where(project_id: event.project_id).delete_all - end - # rubocop: enable CodeReuse/ActiveRecord - def log_event(job_id) logger.event_info( created_at, diff --git a/ee/spec/finders/packages/maven_package_finder_spec.rb b/ee/spec/finders/packages/maven_package_finder_spec.rb index eb198f527c2..0e1792e58ef 100644 --- a/ee/spec/finders/packages/maven_package_finder_spec.rb +++ b/ee/spec/finders/packages/maven_package_finder_spec.rb @@ -6,16 +6,32 @@ describe Packages::MavenPackageFinder do let(:package) { create(:maven_package, project: project) } describe '#execute!' do - it 'returns a package' do - finder = described_class.new(project, package.maven_metadatum.path) + context 'within the project' do + it 'returns a package' do + finder = described_class.new(package.maven_metadatum.path, project) - expect(finder.execute!).to eq(package) + expect(finder.execute!).to eq(package) + end + + it 'raises an error' do + finder = described_class.new('com/example/my-app/1.0-SNAPSHOT', project) + + expect { finder.execute! }.to raise_error(ActiveRecord::RecordNotFound) + end end - it 'raises an error' do - finder = described_class.new(project, 'com/example/my-app/1.0-SNAPSHOT') + context 'across all projects' do + it 'returns a package' do + finder = described_class.new(package.maven_metadatum.path) + + expect(finder.execute!).to eq(package) + end + + it 'raises an error' do + finder = described_class.new('com/example/my-app/1.0-SNAPSHOT') - expect { finder.execute! }.to raise_error(ActiveRecord::RecordNotFound) + expect { finder.execute! }.to raise_error(ActiveRecord::RecordNotFound) + end end end end diff --git a/ee/spec/lib/gitlab/geo/log_cursor/events/repository_deleted_event_spec.rb b/ee/spec/lib/gitlab/geo/log_cursor/events/repository_deleted_event_spec.rb index 24e564e12d7..e9df8cfeca3 100644 --- a/ee/spec/lib/gitlab/geo/log_cursor/events/repository_deleted_event_spec.rb +++ b/ee/spec/lib/gitlab/geo/log_cursor/events/repository_deleted_event_spec.rb @@ -32,10 +32,6 @@ describe Gitlab::Geo::LogCursor::Events::RepositoryDeletedEvent, :postgresql, :c context 'when a tracking entry exists' do let!(:tracking_entry) { create(:geo_project_registry, project: project) } - it 'removes the tracking entry' do - expect { subject.process }.to change(Geo::ProjectRegistry, :count).by(-1) - end - context 'when selective sync is enabled' do let(:secondary) { create(:geo_node, selective_sync_type: 'namespaces', namespaces: [project.namespace]) } @@ -47,6 +43,10 @@ describe Gitlab::Geo::LogCursor::Events::RepositoryDeletedEvent, :postgresql, :c subject.process end + + it 'does not remove the tracking entry' do + expect { subject.process }.not_to change(Geo::ProjectRegistry, :count) + end end end end diff --git a/ee/spec/requests/api/maven_packages_spec.rb b/ee/spec/requests/api/maven_packages_spec.rb index b59f5a2dec4..582bfbd8435 100644 --- a/ee/spec/requests/api/maven_packages_spec.rb +++ b/ee/spec/requests/api/maven_packages_spec.rb @@ -15,6 +15,106 @@ describe API::MavenPackages do stub_licensed_features(packages: true) end + describe 'GET /api/v4/packages/maven/*path/:file_name' do + let(:package) { create(:maven_package, project: project) } + let(:maven_metadatum) { package.maven_metadatum } + let(:package_file_xml) { package.package_files.find_by(file_type: 'xml') } + + context 'a public project' do + it 'returns the file' do + download_file(package_file_xml.file_name) + + expect(response).to have_gitlab_http_status(200) + expect(response.content_type.to_s).to eq('application/octet-stream') + end + + it 'returns sha1 of the file' do + download_file(package_file_xml.file_name + '.sha1') + + expect(response).to have_gitlab_http_status(200) + expect(response.content_type.to_s).to eq('text/plain') + expect(response.body).to eq(package_file_xml.file_sha1) + end + end + + context 'internal project' do + before do + project.team.truncate + project.update!(visibility_level: Gitlab::VisibilityLevel::INTERNAL) + end + + it 'returns the file' do + download_file_with_token(package_file_xml.file_name) + + expect(response).to have_gitlab_http_status(200) + expect(response.content_type.to_s).to eq('application/octet-stream') + end + + it 'denies download when no private token' do + download_file(package_file_xml.file_name) + + expect(response).to have_gitlab_http_status(403) + end + + it 'allows download with job token' do + download_file(package_file_xml.file_name, job_token: job.token) + + expect(response).to have_gitlab_http_status(200) + expect(response.content_type.to_s).to eq('application/octet-stream') + end + end + + context 'private project' do + before do + project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE) + end + + it 'returns the file' do + download_file_with_token(package_file_xml.file_name) + + expect(response).to have_gitlab_http_status(200) + expect(response.content_type.to_s).to eq('application/octet-stream') + end + + it 'denies download when not enough permissions' do + project.add_guest(user) + + download_file_with_token(package_file_xml.file_name) + + expect(response).to have_gitlab_http_status(403) + end + + it 'denies download when no private token' do + download_file(package_file_xml.file_name) + + expect(response).to have_gitlab_http_status(403) + end + + it 'allows download with job token' do + download_file(package_file_xml.file_name, job_token: job.token) + + expect(response).to have_gitlab_http_status(200) + expect(response.content_type.to_s).to eq('application/octet-stream') + end + end + + it 'rejects request if feature is not in the license' do + stub_licensed_features(packages: false) + + download_file(package_file_xml.file_name) + + expect(response).to have_gitlab_http_status(403) + end + + def download_file(file_name, params = {}, request_headers = headers) + get api("/packages/maven/#{maven_metadatum.path}/#{file_name}"), params, request_headers + end + + def download_file_with_token(file_name, params = {}, request_headers = headers_with_token) + download_file(file_name, params, request_headers) + end + end + describe 'GET /api/v4/projects/:id/packages/maven/*path/:file_name' do let(:package) { create(:maven_package, project: project) } let(:maven_metadatum) { package.maven_metadatum } diff --git a/ee/spec/services/geo/repository_destroy_service_spec.rb b/ee/spec/services/geo/repository_destroy_service_spec.rb index 965e8569f58..e29fd272b17 100644 --- a/ee/spec/services/geo/repository_destroy_service_spec.rb +++ b/ee/spec/services/geo/repository_destroy_service_spec.rb @@ -27,6 +27,12 @@ describe Geo::RepositoryDestroyService do service.execute end + it 'removes the tracking entry' do + create(:geo_project_registry, project: project) + + expect { service.execute }.to change(Geo::ProjectRegistry, :count).by(-1) + end + context 'legacy storage project' do let(:project) { create(:project_empty_repo, :legacy_storage) } |