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
......
...@@ -6,8 +6,6 @@ module API ...@@ -6,8 +6,6 @@ module API
module DebianPackageEndpoints module DebianPackageEndpoints
extend ActiveSupport::Concern extend ActiveSupport::Concern
LETTER_REGEX = %r{(lib)?[a-z0-9]}.freeze
PACKAGE_REGEX = API::NO_SLASH_URL_PART_REGEX
DISTRIBUTION_REQUIREMENTS = { DISTRIBUTION_REQUIREMENTS = {
distribution: ::Packages::Debian::DISTRIBUTION_REGEX distribution: ::Packages::Debian::DISTRIBUTION_REGEX
}.freeze }.freeze
...@@ -15,14 +13,6 @@ module API ...@@ -15,14 +13,6 @@ module API
component: ::Packages::Debian::COMPONENT_REGEX, component: ::Packages::Debian::COMPONENT_REGEX,
architecture: ::Packages::Debian::ARCHITECTURE_REGEX architecture: ::Packages::Debian::ARCHITECTURE_REGEX
}.freeze }.freeze
COMPONENT_LETTER_SOURCE_PACKAGE_REQUIREMENTS = {
component: ::Packages::Debian::COMPONENT_REGEX,
letter: LETTER_REGEX,
source_package: PACKAGE_REGEX
}.freeze
FILE_NAME_REQUIREMENTS = {
file_name: API::NO_SLASH_URL_PART_REGEX
}.freeze
included do included do
feature_category :package_registry feature_category :package_registry
...@@ -31,25 +21,46 @@ module API ...@@ -31,25 +21,46 @@ module API
helpers ::API::Helpers::Packages::BasicAuthHelpers helpers ::API::Helpers::Packages::BasicAuthHelpers
include ::API::Helpers::Authentication include ::API::Helpers::Authentication
namespace 'packages/debian' do helpers do
params :shared_package_file_params do
requires :distribution, type: String, desc: 'The Debian Codename or Suite', regexp: Gitlab::Regex.debian_distribution_regex
requires :letter, type: String, desc: 'The Debian Classification (first-letter or lib-first-letter)'
requires :package_name, type: String, desc: 'The Debian Source Package Name', regexp: Gitlab::Regex.debian_package_name_regex
requires :package_version, type: String, desc: 'The Debian Source Package Version', regexp: Gitlab::Regex.debian_version_regex
requires :file_name, type: String, desc: 'The Debian File Name'
end
def distribution_from!(container)
::Packages::Debian::DistributionsFinder.new(container, codename_or_suite: params[:distribution]).execute.last!
end
def present_package_file!
not_found! unless params[:package_name].start_with?(params[:letter])
package_file = distribution_from!(user_project).package_files.with_file_name(params[:file_name]).last!
present_carrierwave_file!(package_file.file)
end
end
authenticate_with do |accept| authenticate_with do |accept|
accept.token_types(:personal_access_token, :deploy_token, :job_token) accept.token_types(:personal_access_token, :deploy_token, :job_token)
.sent_through(:http_basic_auth) .sent_through(:http_basic_auth)
end end
helpers do rescue_from ArgumentError do |e|
def present_release_file render_api_error!(e.message, 400)
distribution = ::Packages::Debian::DistributionsFinder.new(project_or_group, codename_or_suite: params[:distribution]).execute.last!
present_carrierwave_file!(distribution.file)
end end
rescue_from ActiveRecord::RecordInvalid do |e|
render_api_error!(e.message, 400)
end end
format :txt format :txt
content_type :txt, 'text/plain' content_type :txt, 'text/plain'
params do params do
requires :distribution, type: String, desc: 'The Debian Codename', regexp: Gitlab::Regex.debian_distribution_regex requires :distribution, type: String, desc: 'The Debian Codename or Suite', regexp: Gitlab::Regex.debian_distribution_regex
end end
namespace 'dists/*distribution', requirements: DISTRIBUTION_REQUIREMENTS do namespace 'dists/*distribution', requirements: DISTRIBUTION_REQUIREMENTS do
...@@ -70,7 +81,7 @@ module API ...@@ -70,7 +81,7 @@ module API
route_setting :authentication, authenticate_non_public: true route_setting :authentication, authenticate_non_public: true
get 'Release' do get 'Release' do
present_release_file present_carrierwave_file!(distribution_from!(project_or_group).file)
end end
# GET {projects|groups}/:id/packages/debian/dists/*distribution/InRelease # GET {projects|groups}/:id/packages/debian/dists/*distribution/InRelease
...@@ -81,7 +92,7 @@ module API ...@@ -81,7 +92,7 @@ module API
route_setting :authentication, authenticate_non_public: true route_setting :authentication, authenticate_non_public: true
get 'InRelease' do get 'InRelease' do
# Signature to be added in 7.3 of https://gitlab.com/groups/gitlab-org/-/epics/6057#note_582697034 # Signature to be added in 7.3 of https://gitlab.com/groups/gitlab-org/-/epics/6057#note_582697034
present_release_file present_carrierwave_file!(distribution_from!(project_or_group).file)
end end
params do params do
...@@ -114,29 +125,6 @@ module API ...@@ -114,29 +125,6 @@ module API
end end
end end
end end
params do
requires :component, type: String, desc: 'The Debian Component', regexp: Gitlab::Regex.debian_component_regex
requires :letter, type: String, desc: 'The Debian Classification (first-letter or lib-first-letter)'
requires :source_package, type: String, desc: 'The Debian Source Package Name', regexp: Gitlab::Regex.debian_package_name_regex
end
namespace 'pool/:component/:letter/:source_package', requirements: COMPONENT_LETTER_SOURCE_PACKAGE_REQUIREMENTS do
# GET {projects|groups}/:id/packages/debian/pool/:component/:letter/:source_package/:file_name
params do
requires :file_name, type: String, desc: 'The Debian File Name'
end
desc 'The package' do
detail 'This feature was introduced in GitLab 13.5'
end
route_setting :authentication, authenticate_non_public: true
get ':file_name', requirements: FILE_NAME_REQUIREMENTS do
# https://gitlab.com/gitlab-org/gitlab/-/issues/5835#note_414103286
'TODO File'
end
end
end
end end
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
@project ||= find_project!(params[:project_id])
end 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
user_group
end
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
user_project
end 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
user_project
end
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
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
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