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. ...@@ -119,6 +119,50 @@ on the home page of your project.
If you have a self-hosted GitLab installation, replace `gitlab.com` with your If you have a self-hosted GitLab installation, replace `gitlab.com` with your
domain name. 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 ## Uploading packages
Once you have set up the [authorization](#authorizing-with-the-gitlab-maven-repository) Once you have set up the [authorization](#authorizing-with-the-gitlab-maven-repository)
......
# frozen_string_literal: true # frozen_string_literal: true
class Packages::MavenPackageFinder class Packages::MavenPackageFinder
attr_reader :project, :path attr_reader :path, :project
def initialize(project, path) def initialize(path, project = nil)
@project = project
@path = path @path = path
@project = project
end end
def execute def execute
...@@ -17,9 +17,17 @@ class Packages::MavenPackageFinder ...@@ -17,9 +17,17 @@ class Packages::MavenPackageFinder
private private
def scope
if project
project.packages
else
::Packages::Package.all
end
end
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def packages def packages
project.packages.joins(:maven_metadatum) scope.joins(:maven_metadatum)
.where(packages_maven_metadata: { path: path }) .where(packages_maven_metadata: { path: path })
end end
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
......
...@@ -5,7 +5,7 @@ module Packages ...@@ -5,7 +5,7 @@ module Packages
def execute def execute
package = ::Packages::MavenPackageFinder package = ::Packages::MavenPackageFinder
.new(project, params[:path]).execute .new(params[:path], project).execute
unless package unless package
if params[:file_name] == MAVEN_METADATA_FILE 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 ...@@ -12,7 +12,6 @@ module API
before do before do
require_packages_enabled! require_packages_enabled!
authenticate_non_get! authenticate_non_get!
authorize_packages_feature!
end end
helpers do helpers do
...@@ -52,12 +51,56 @@ module API ...@@ -52,12 +51,56 @@ module API
conflict! conflict!
end end
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 end
params do params do
requires :id, type: String, desc: 'The ID of a project' requires :id, type: String, desc: 'The ID of a project'
end end
resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
before do
authorize_packages_feature!
end
desc 'Download the maven package file' do desc 'Download the maven package file' do
detail 'This feature was introduced in GitLab 11.3' detail 'This feature was introduced in GitLab 11.3'
end end
...@@ -72,7 +115,7 @@ module API ...@@ -72,7 +115,7 @@ module API
file_name, format = extract_format(params[:file_name]) file_name, format = extract_format(params[:file_name])
package = ::Packages::MavenPackageFinder package = ::Packages::MavenPackageFinder
.new(user_project, params[:path]).execute! .new(params[:path], user_project).execute!
package_file = ::Packages::PackageFileFinder package_file = ::Packages::PackageFileFinder
.new(package, file_name).execute! .new(package, file_name).execute!
......
...@@ -3,11 +3,15 @@ FactoryBot.define do ...@@ -3,11 +3,15 @@ FactoryBot.define do
factory :package, class: Packages::Package do factory :package, class: Packages::Package do
project project
name 'my/company/app/my-app' name 'my/company/app/my-app'
version '1-0-SNAPSHOT' version '1.0-SNAPSHOT'
factory :maven_package do factory :maven_package do
maven_metadatum maven_metadatum
after :build do |package|
package.maven_metadatum.path = "#{package.name}/#{package.version}"
end
after :create do |package| after :create do |package|
create :package_file, :xml, package: package create :package_file, :xml, package: package
create :package_file, :jar, package: package create :package_file, :jar, package: package
......
...@@ -6,16 +6,32 @@ describe Packages::MavenPackageFinder do ...@@ -6,16 +6,32 @@ describe Packages::MavenPackageFinder do
let(:package) { create(:maven_package, project: project) } let(:package) { create(:maven_package, project: project) }
describe '#execute!' do describe '#execute!' do
context 'within the project' do
it 'returns a package' do it 'returns a package' do
finder = described_class.new(project, package.maven_metadatum.path) finder = described_class.new(package.maven_metadatum.path, project)
expect(finder.execute!).to eq(package) expect(finder.execute!).to eq(package)
end end
it 'raises an error' do it 'raises an error' do
finder = described_class.new(project, 'com/example/my-app/1.0-SNAPSHOT') finder = described_class.new('com/example/my-app/1.0-SNAPSHOT', project)
expect { finder.execute! }.to raise_error(ActiveRecord::RecordNotFound) expect { finder.execute! }.to raise_error(ActiveRecord::RecordNotFound)
end end
end end
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)
end
end
end
end end
...@@ -15,6 +15,116 @@ describe API::MavenPackages do ...@@ -15,6 +15,116 @@ describe API::MavenPackages do
stub_licensed_features(packages: true) stub_licensed_features(packages: true)
end 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 describe 'GET /api/v4/projects/:id/packages/maven/*path/:file_name' do
let(:package) { create(:maven_package, project: project) } let(:package) { create(:maven_package, project: project) }
let(:maven_metadatum) { package.maven_metadatum } 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