Commit 7b556982 authored by Douglas Barbosa Alexandre's avatar Douglas Barbosa Alexandre

Merge branch 'dz-instance-level-maven-endpoint' into 'master'

Instance level Maven endpoint (second attempt)

Closes #7769

See merge request gitlab-org/gitlab-ee!8757
parents fc7b4a83 8a84c7e7
......@@ -119,6 +119,50 @@ 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.7.
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 and the packages you have access to will be available
for download.
Note that only packages that have the same path as the project are exposed via
the instance level endpoint.
| Project | Package | Instance level endpoint available |
| ------- | ------- | --------------------------------- |
| `foo/bar` | `foo/bar/1.0-SNAPSHOT` | Yes |
| `gitlab-org/gitlab-ce` | `foo/bar/1.0-SNAPSHOT` | No |
| `gitlab-org/gitlab-ce` | `gitlab-org/gitlab-ce/1.0-SNAPSHOT` | Yes |
The example below shows how the relevant `repository` section of your `pom.xml`
would look like. You still need a project specific URL for uploading a package in
the `distributionManagement` section:
```xml
<repositories>
<repository>
<id>gitlab-maven</id>
<url>https://gitlab.com/api/v4/packages/maven</url>
</repository>
</repositories>
<distributionManagement>
<repository>
<id>gitlab-maven</id>
<url>https://gitlab.com/api/v4/projects/PROJECT_ID/packages/maven</url>
</repository>
<snapshotRepository>
<id>gitlab-maven</id>
<url>https://gitlab.com/api/v4/projects/PROJECT_ID/packages/maven</url>
</snapshotRepository>
</distributionManagement>
```
If you have a self-hosted GitLab installation, replace `gitlab.com` with your
domain name.
## Uploading packages
Once you have set up the [authorization](#authorizing-with-the-gitlab-maven-repository)
......
# 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
......
......@@ -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
......
---
title: Add an instance-level endpoint for downloading maven packages
merge_request: 8274
author:
type: added
......@@ -12,7 +12,6 @@ module API
before do
require_packages_enabled!
authenticate_non_get!
authorize_packages_feature!
end
helpers do
......@@ -52,12 +51,56 @@ module API
conflict!
end
end
def find_project_by_path(path)
project_path = path.rpartition('/').first
Project.find_by_full_path(project_path)
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])
# To avoid name collision we require project path and project package be the same.
# For packages that have different name from the project we should use
# the endpoint that includes project id
project = find_project_by_path(params[:path])
authorize!(:read_package, project)
package = ::Packages::MavenPackageFinder.new(params[:path], project).execute!
forbidden! unless package.project.feature_available?(:packages)
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::NAMESPACE_OR_PROJECT_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 +115,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!
......
......@@ -3,11 +3,15 @@ FactoryBot.define do
factory :package, class: Packages::Package do
project
name 'my/company/app/my-app'
version '1-0-SNAPSHOT'
version '1.0-SNAPSHOT'
factory :maven_package do
maven_metadatum
after :build do |package|
package.maven_metadatum.path = "#{package.name}/#{package.version}"
end
after :create do |package|
create :package_file, :xml, package: package
create :package_file, :jar, package: package
......
......@@ -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
......@@ -15,6 +15,116 @@ 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, name: project.full_path) }
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
context 'project name is different from a package name' do
let(:package) { create(:maven_package, project: project) }
it 'rejects request' do
download_file(package_file_xml.file_name)
expect(response).to have_gitlab_http_status(403)
end
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 }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment