Commit 7dfd9546 authored by David Fernandez's avatar David Fernandez

Merge branch 'debian_download_file' into 'master'

Add Debian API endpoint for deb, udeb, ... files

See merge request gitlab-org/gitlab!64923
parents 9d8aaa13 6a576a69
...@@ -6,6 +6,8 @@ module Packages ...@@ -6,6 +6,8 @@ module Packages
COMPONENT_REGEX = DISTRIBUTION_REGEX.freeze COMPONENT_REGEX = DISTRIBUTION_REGEX.freeze
ARCHITECTURE_REGEX = %r{[a-z0-9][-a-z0-9]*}.freeze ARCHITECTURE_REGEX = %r{[a-z0-9][-a-z0-9]*}.freeze
LETTER_REGEX = %r{(lib)?[a-z0-9]}.freeze
def self.table_name_prefix def self.table_name_prefix
'packages_debian_' 'packages_debian_'
end end
......
...@@ -120,7 +120,7 @@ module Packages ...@@ -120,7 +120,7 @@ module Packages
def package_filename(package_file) def package_filename(package_file)
letter = package_file.package.name.start_with?('lib') ? package_file.package.name[0..3] : package_file.package.name[0] letter = package_file.package.name.start_with?('lib') ? package_file.package.name[0..3] : package_file.package.name[0]
"#{pool_prefix(package_file)}/#{letter}/#{package_file.package.name}/#{package_file.file_name}" "#{pool_prefix(package_file)}/#{letter}/#{package_file.package.name}/#{package_file.package.version}/#{package_file.file_name}"
end end
def pool_prefix(package_file) def pool_prefix(package_file)
...@@ -128,7 +128,7 @@ module Packages ...@@ -128,7 +128,7 @@ module Packages
when ::Packages::Debian::GroupDistribution when ::Packages::Debian::GroupDistribution
"pool/#{@distribution.codename}/#{package_file.package.project_id}" "pool/#{@distribution.codename}/#{package_file.package.project_id}"
else else
"pool/#{@distribution.codename}/#{@distribution.container_id}" "pool/#{@distribution.codename}"
end end
end end
......
...@@ -2,20 +2,22 @@ ...@@ -2,20 +2,22 @@
module API module API
class DebianGroupPackages < ::API::Base class DebianGroupPackages < ::API::Base
params do PACKAGE_FILE_REQUIREMENTS = ::API::DebianProjectPackages::PACKAGE_FILE_REQUIREMENTS.merge(
requires :id, type: String, desc: 'The ID of a group' project_id: %r{[0-9]+}.freeze
end ).freeze
resource :groups, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do resource :groups, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
rescue_from ArgumentError do |e| helpers do
render_api_error!(e.message, 400) def user_project
end @project ||= find_project!(params[:project_id])
end
rescue_from ActiveRecord::RecordInvalid do |e| def project_or_group
render_api_error!(e.message, 400) user_group
end
end end
before do after_validation do
require_packages_enabled! require_packages_enabled!
not_found! unless ::Feature.enabled?(:debian_group_packages, user_group) not_found! unless ::Feature.enabled?(:debian_group_packages, user_group)
...@@ -23,14 +25,27 @@ module API ...@@ -23,14 +25,27 @@ module API
authorize_read_package!(user_group) authorize_read_package!(user_group)
end end
namespace ':id/-' do params do
helpers do requires :id, type: String, desc: 'The ID of a group'
def project_or_group end
user_group
end
end
namespace ':id/-/packages/debian' do
include ::API::Concerns::Packages::DebianPackageEndpoints include ::API::Concerns::Packages::DebianPackageEndpoints
# GET groups/:id/packages/debian/pool/:distribution/:project_id/:letter/:package_name/:package_version/:file_name
params do
requires :project_id, type: Integer, desc: 'The Project Id'
use :shared_package_file_params
end
desc 'The package' do
detail 'This feature was introduced in GitLab 14.2'
end
route_setting :authentication, authenticate_non_public: true
get 'pool/:distribution/:project_id/:letter/:package_name/:package_version/:file_name', requirements: PACKAGE_FILE_REQUIREMENTS do
present_package_file!
end
end end
end end
end end
......
...@@ -2,17 +2,23 @@ ...@@ -2,17 +2,23 @@
module API module API
class DebianProjectPackages < ::API::Base class DebianProjectPackages < ::API::Base
params do PACKAGE_FILE_REQUIREMENTS = {
requires :id, type: String, desc: 'The ID of a project' id: API::NO_SLASH_URL_PART_REGEX,
end distribution: ::Packages::Debian::DISTRIBUTION_REGEX,
letter: ::Packages::Debian::LETTER_REGEX,
package_name: API::NO_SLASH_URL_PART_REGEX,
package_version: API::NO_SLASH_URL_PART_REGEX,
file_name: API::NO_SLASH_URL_PART_REGEX
}.freeze
FILE_NAME_REQUIREMENTS = {
file_name: API::NO_SLASH_URL_PART_REGEX
}.freeze
resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
rescue_from ArgumentError do |e| helpers do
render_api_error!(e.message, 400) def project_or_group
end user_project
end
rescue_from ActiveRecord::RecordInvalid do |e|
render_api_error!(e.message, 400)
end end
after_validation do after_validation do
...@@ -23,20 +29,32 @@ module API ...@@ -23,20 +29,32 @@ module API
authorize_read_package! authorize_read_package!
end end
namespace ':id' do params do
helpers do requires :id, type: String, desc: 'The ID of a project'
def project_or_group end
user_project
end
end
namespace ':id/packages/debian' do
include ::API::Concerns::Packages::DebianPackageEndpoints include ::API::Concerns::Packages::DebianPackageEndpoints
# GET projects/:id/packages/debian/pool/:distribution/:letter/:package_name/:package_version/:file_name
params do
use :shared_package_file_params
end
desc 'The package' do
detail 'This feature was introduced in GitLab 14.2'
end
route_setting :authentication, authenticate_non_public: true
get 'pool/:distribution/:letter/:package_name/:package_version/:file_name', requirements: PACKAGE_FILE_REQUIREMENTS do
present_package_file!
end
params do params do
requires :file_name, type: String, desc: 'The file name' requires :file_name, type: String, desc: 'The file name'
end end
namespace 'packages/debian/:file_name', requirements: FILE_NAME_REQUIREMENTS do namespace ':file_name', requirements: FILE_NAME_REQUIREMENTS do
format :txt format :txt
content_type :json, Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE content_type :json, Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE
......
...@@ -6,6 +6,12 @@ RSpec.describe API::DebianGroupPackages do ...@@ -6,6 +6,12 @@ RSpec.describe API::DebianGroupPackages do
include WorkhorseHelpers include WorkhorseHelpers
include_context 'Debian repository shared context', :group, false do include_context 'Debian repository shared context', :group, false do
context 'with invalid parameter' do
let(:url) { "/groups/1/-/packages/debian/dists/with+space/InRelease" }
it_behaves_like 'Debian repository GET request', :bad_request, /^distribution is invalid$/
end
describe 'GET groups/:id/-/packages/debian/dists/*distribution/Release.gpg' do describe 'GET groups/:id/-/packages/debian/dists/*distribution/Release.gpg' do
let(:url) { "/groups/#{container.id}/-/packages/debian/dists/#{distribution.codename}/Release.gpg" } let(:url) { "/groups/#{container.id}/-/packages/debian/dists/#{distribution.codename}/Release.gpg" }
...@@ -30,10 +36,25 @@ RSpec.describe API::DebianGroupPackages do ...@@ -30,10 +36,25 @@ RSpec.describe API::DebianGroupPackages do
it_behaves_like 'Debian repository read endpoint', 'GET request', :success, /Description: This is an incomplete Packages file/ it_behaves_like 'Debian repository read endpoint', 'GET request', :success, /Description: This is an incomplete Packages file/
end end
describe 'GET groups/:id/-/packages/debian/pool/:component/:letter/:source_package/:file_name' do describe 'GET groups/:id/-/packages/debian/pool/:codename/:project_id/:letter/:package_name/:package_version/:file_name' do
let(:url) { "/groups/#{container.id}/-/packages/debian/pool/#{component.name}/#{letter}/#{source_package}/#{package_name}_#{package_version}_#{architecture.name}.deb" } let(:url) { "/groups/#{container.id}/-/packages/debian/pool/#{package.debian_distribution.codename}/#{project.id}/#{letter}/#{package.name}/#{package.version}/#{file_name}" }
using RSpec::Parameterized::TableSyntax
where(:file_name, :success_body) do
'sample_1.2.3~alpha2.tar.xz' | /^.7zXZ/
'sample_1.2.3~alpha2.dsc' | /^Format: 3.0 \(native\)/
'libsample0_1.2.3~alpha2_amd64.deb' | /^!<arch>/
'sample-udeb_1.2.3~alpha2_amd64.udeb' | /^!<arch>/
'sample_1.2.3~alpha2_amd64.buildinfo' | /Build-Tainted-By/
'sample_1.2.3~alpha2_amd64.changes' | /urgency=medium/
end
with_them do
include_context 'with file_name', params[:file_name]
it_behaves_like 'Debian repository read endpoint', 'GET request', :success, /^TODO File$/ it_behaves_like 'Debian repository read endpoint', 'GET request', :success, params[:success_body]
end
end end
end end
end end
...@@ -6,6 +6,12 @@ RSpec.describe API::DebianProjectPackages do ...@@ -6,6 +6,12 @@ RSpec.describe API::DebianProjectPackages do
include WorkhorseHelpers include WorkhorseHelpers
include_context 'Debian repository shared context', :project, true do include_context 'Debian repository shared context', :project, true do
context 'with invalid parameter' do
let(:url) { "/projects/1/packages/debian/dists/with+space/InRelease" }
it_behaves_like 'Debian repository GET request', :bad_request, /^distribution is invalid$/
end
describe 'GET projects/:id/packages/debian/dists/*distribution/Release.gpg' do describe 'GET projects/:id/packages/debian/dists/*distribution/Release.gpg' do
let(:url) { "/projects/#{container.id}/packages/debian/dists/#{distribution.codename}/Release.gpg" } let(:url) { "/projects/#{container.id}/packages/debian/dists/#{distribution.codename}/Release.gpg" }
...@@ -30,10 +36,25 @@ RSpec.describe API::DebianProjectPackages do ...@@ -30,10 +36,25 @@ RSpec.describe API::DebianProjectPackages do
it_behaves_like 'Debian repository read endpoint', 'GET request', :success, /Description: This is an incomplete Packages file/ it_behaves_like 'Debian repository read endpoint', 'GET request', :success, /Description: This is an incomplete Packages file/
end end
describe 'GET projects/:id/packages/debian/pool/:component/:letter/:source_package/:file_name' do describe 'GET projects/:id/packages/debian/pool/:codename/:letter/:package_name/:package_version/:file_name' do
let(:url) { "/projects/#{container.id}/packages/debian/pool/#{component.name}/#{letter}/#{source_package}/#{package_name}_#{package_version}_#{architecture.name}.deb" } let(:url) { "/projects/#{container.id}/packages/debian/pool/#{package.debian_distribution.codename}/#{letter}/#{package.name}/#{package.version}/#{file_name}" }
using RSpec::Parameterized::TableSyntax
it_behaves_like 'Debian repository read endpoint', 'GET request', :success, /^TODO File$/ where(:file_name, :success_body) do
'sample_1.2.3~alpha2.tar.xz' | /^.7zXZ/
'sample_1.2.3~alpha2.dsc' | /^Format: 3.0 \(native\)/
'libsample0_1.2.3~alpha2_amd64.deb' | /^!<arch>/
'sample-udeb_1.2.3~alpha2_amd64.udeb' | /^!<arch>/
'sample_1.2.3~alpha2_amd64.buildinfo' | /Build-Tainted-By/
'sample_1.2.3~alpha2_amd64.changes' | /urgency=medium/
end
with_them do
include_context 'with file_name', params[:file_name]
it_behaves_like 'Debian repository read endpoint', 'GET request', :success, params[:success_body]
end
end end
describe 'PUT projects/:id/packages/debian/:file_name' do describe 'PUT projects/:id/packages/debian/:file_name' do
......
...@@ -29,6 +29,8 @@ RSpec.shared_context 'Debian repository shared context' do |container_type, can_ ...@@ -29,6 +29,8 @@ RSpec.shared_context 'Debian repository shared context' do |container_type, can_
let_it_be(:public_project) { create(:project, :public, group: public_container) } let_it_be(:public_project) { create(:project, :public, group: public_container) }
let_it_be(:private_project_distribution) { create(:debian_project_distribution, container: private_project, codename: 'existing-codename') } let_it_be(:private_project_distribution) { create(:debian_project_distribution, container: private_project, codename: 'existing-codename') }
let_it_be(:public_project_distribution) { create(:debian_project_distribution, container: public_project, codename: 'existing-codename') } let_it_be(:public_project_distribution) { create(:debian_project_distribution, container: public_project, codename: 'existing-codename') }
let(:project) { { private: private_project, public: public_project }[visibility_level] }
else else
let_it_be(:private_project) { private_container } let_it_be(:private_project) { private_container }
let_it_be(:public_project) { public_container } let_it_be(:public_project) { public_container }
...@@ -45,12 +47,8 @@ RSpec.shared_context 'Debian repository shared context' do |container_type, can_ ...@@ -45,12 +47,8 @@ RSpec.shared_context 'Debian repository shared context' do |container_type, can_
let(:architecture) { { private: private_architecture, public: public_architecture }[visibility_level] } let(:architecture) { { private: private_architecture, public: public_architecture }[visibility_level] }
let(:component) { { private: private_component, public: public_component }[visibility_level] } let(:component) { { private: private_component, public: public_component }[visibility_level] }
let(:component_file) { { private: private_component_file, public: public_component_file }[visibility_level] } let(:component_file) { { private: private_component_file, public: public_component_file }[visibility_level] }
let(:package) { { private: private_package, public: public_package }[visibility_level] }
let(:source_package) { 'sample' } let(:letter) { package.name[0..2] == 'lib' ? package.name[0..3] : package.name[0] }
let(:letter) { source_package[0..2] == 'lib' ? source_package[0..3] : source_package[0] }
let(:package_name) { 'libsample0' }
let(:package_version) { '1.2.3~alpha2' }
let(:file_name) { "#{package_name}_#{package_version}_#{architecture.name}.deb" }
let(:method) { :get } let(:method) { :get }
...@@ -94,6 +92,10 @@ RSpec.shared_context 'Debian repository shared context' do |container_type, can_ ...@@ -94,6 +92,10 @@ RSpec.shared_context 'Debian repository shared context' do |container_type, can_
end end
end end
RSpec.shared_context 'with file_name' do |file_name|
let(:file_name) { file_name }
end
RSpec.shared_context 'Debian repository auth headers' do |user_role, user_token, auth_method = :token| RSpec.shared_context 'Debian repository auth headers' do |user_role, user_token, auth_method = :token|
let(:token) { user_token ? personal_access_token.token : 'wrong' } let(:token) { user_token ? personal_access_token.token : 'wrong' }
......
...@@ -53,7 +53,9 @@ RSpec.shared_examples 'Generate Debian Distribution and component files' do ...@@ -53,7 +53,9 @@ RSpec.shared_examples 'Generate Debian Distribution and component files' do
.and change { component_file1.reload.updated_at }.to(current_time.round) .and change { component_file1.reload.updated_at }.to(current_time.round)
debs = package.package_files.with_debian_file_type(:deb).preload_debian_file_metadata.to_a debs = package.package_files.with_debian_file_type(:deb).preload_debian_file_metadata.to_a
pool_prefix = "pool/unstable/#{project.id}/p/#{package.name}" pool_prefix = 'pool/unstable'
pool_prefix += "/#{project.id}" if container_type == :group
pool_prefix += "/p/#{package.name}/#{package.version}"
expected_main_amd64_content = <<~EOF expected_main_amd64_content = <<~EOF
Package: libsample0 Package: libsample0
Source: #{package.name} Source: #{package.name}
......
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